This Skill usesprojectas its scope, and produces a checklist against Microsoft Learn's Azure Pipelines security recommendations. It only calls GET throughout and does not modify any settings; for remediation it always provides web UI steps for the user to handle themselves.
Project selection
-
If the user provides a project name (for example "Contoso project"), use it directly without calling the project list API.
-
If not provided, ask the user to provide it once.
-
If 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" -
Do not continue until the user provides or selects a project.
Connection and authentication
Always use curl to call the Azure DevOps REST API directly; each command carries 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 using Basic authentication:
-u ":$AZURE_DEVOPS_PAT"; if onlyAZURE_DEVOPS_EXT_PATis set (the variable used by the Azure CLI azure-devops extension), use it instead. When using a Microsoft Entra ID token instead, 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, and stop. Only read the token from environment variables: 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: Project and Team (Read), Service Connections (Read), Build (Read), Variable Groups (Read), Agent Pools (Read), Secure Files (Read), Code (Read); listing environments additionally requires Environment (Read & manage) (optional, see step 3). For the scope corresponding to each check, see "Checks". This Skill does not require 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 expansion by the shell. - Errors: HTTP 401, 403, 203, or an HTML login page in the response means the token is invalid, expired, or lacks permissions; report it and stop. HTTP 404 means the organization, project, or resource ID is incorrect. Exception: after the project settings have been retrieved successfully, if the API for a certain check returns 403, mark only that check as ❓ and note the missing permission scope, then continue with the other checks.
- 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".
Checks
| Check | API used | PAT permission scope | Basis (Microsoft Learn) |
|---|---|---|---|
| A. The project's Pipeline settings | build/generalsettings |
Project and Team (Read) | Job access tokens、Secure Azure Pipelines、Approach |
| B. Authentication method, scope, and idleness of service connections | serviceendpoint/endpoints、executionhistory |
Service Connections (Read) | Overview、Misc |
| C. Whether protected resources are open to all pipelines | pipelines/pipelinepermissions |
Build (Read), and also requires the scope of each resource list | Resources |
| D. Approval and checks for production environment resources | pipelines/checks/configurations |
Build (Read) | Resources、Overview |
Judgment symbols: ✅ meets recommendation, ❌ does not meet, ❓ cannot be determined (field does not exist, insufficient permissions, or requires manual confirmation).
Steps
1. Project's Pipeline settings (A)
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/build/generalsettings?api-version=7.1"
The recommended value for each field is true; when a field does not exist, mark it ❓, do not treat it as false:
| Field | UI setting name | Basis |
|---|---|---|
enforceJobAuthScope |
Limit job authorization scope to current project for non-release pipelines | access-tokens |
enforceJobAuthScopeForReleases |
Limit job authorization scope to current project for release pipelines | access-tokens |
enforceReferencedRepoScopedToken |
Protect access to repositories in YAML pipelines | access-tokens |
enforceSettableVar |
Limit variables that can be set at queue time | overview |
enableShellTasksArgsSanitizing |
Enable shell tasks arguments parameter validation | overview |
disableClassicBuildPipelineCreation |
Disable creation of classic build pipelines | approach |
disableClassicReleasePipelineCreation |
Disable creation of classic release pipelines | approach |
enforceNoAccessToSecretsFromForks |
Do not let fork builds obtain secrets (see "Don't provide secrets to fork builds" in the overview) | overview、misc |
- Organization-level settings have no REST API (
{org}/_apis/build/generalsettingsreturns 404). When enabled at the organization level, it applies to all projects, so items whose project-level value isfalseare marked ❌, and ask the user to go to Organization settings > Pipelines > Settings(https://dev.azure.com/{org}/_settings/pipelinessettings) to confirm whether it has been enabled at the organization level. Learn recommends limiting the job authorization scope at the organization level. - The job authorization scope for public projects is always limited to the project, regardless of the setting value; note this in the results.
2. Service connections (B)
-
List service connections:
curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/serviceendpoint/endpoints?api-version=7.1"Note that
id、name、type、authorization.scheme、isShared、isReady、isDisabled、serviceEndpointProjectReferences。typemay have inconsistent casing (for example,azurermandAzureRM); comparisons are case-insensitive. The list does not contain any credential contents, and do not attempt to obtain them. -
Assess one by one:
- Authentication method(
typeisazurerm):authorization.schemeisWorkloadIdentityFederationmark ✅;ServicePrincipal、PublishProfileand other ways of using secrets are marked ❌;ManagedServiceIdentitydoes not store secrets, but the permissions of the agent identity need to be confirmed, mark ❓. - Scope(
azurerm):authorization.parameters.scopecontains/resourcegroups/mark ✅; only down to the subscription or management group mark ❌ (Learn recommends selecting a specific resource group); if this field is absent (mostly manually created) mark ❓. - Readiness:
isReadyisfalsemark ❌;isDisabledistruemark as "disabled", skip the remaining checks. - Shared across projects:
isSharedistrueorserviceEndpointProjectReferenceswhen there is more than one, note the number of projects sharing it and remind the user to confirm that every project needs it.
- Authentication method(
-
Idle check: take the most recent usage record (sorted by time from newest to oldest):
curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/serviceendpoint/{endpointId}/executionhistory?top=1&api-version=7.1"value[0].data.finishTime(if none, usestartTime) mark ❌ if more than 100 days ago;valueempty means no usage record can be found, mark ❓ and suggest confirming whether it is still needed.data.planType(for example,Build、Release) anddata.definition.namecan show the pipeline that last used it. Basis: Learn recommends periodically reviewing Usage history, and explains that Azure Pipelines begins automatically disabling service connections idle for 100 days.
3. List other protected resources
Obtain the lists in order; when a list returns 403, mark that resource type as ❓ (write the missing permission scope in parentheses):
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/distributedtask/variablegroups?api-version=7.1"
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/distributedtask/securefiles?api-version=7.1-preview.1"
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/distributedtask/queues?api-version=7.1"
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/git/repositories?api-version=7.1"
-
Variable groups (Variable Groups (Read)): note
id、name、type(VstsorAzureKeyVault), and whether there is a variable withvariables.*.isSecretastrue; do not output any variable values. -
Secure Files (Read): The Learn documentation only lists the preview version, here provide
7.1-preview.1; note downid、name。 -
Agent Pools (Read): note down
id、name、pool.isHosted(trueis Microsoft-hosted). -
Repositories (Code (Read)): note down
idandproject.id。 -
Environments (optional): The API for listing environments only accepts the Environment (Read & manage) scope, which has manage permissions; there is no read-only scope available. Call it only when the PAT has this scope; if not, skip the listing and mark ❓, or ask the user to provide the environment ID from the environment URL (
/_environments/{id}); steps 4 and 5 only need Build (Read) for environments.curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/distributedtask/environments?%24top=100&api-version=7.1"
4. Open access (C)
For each service connection, variable group, secure file, queue, repository, and environment, query pipeline permissions (only the preview version, must include 7.1-preview.1; including 7.1 returns 400):
curl -sS -u ":$AZURE_DEVOPS_PAT" \
"https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/pipelines/pipelinepermissions/{resourceType}/{resourceId}?api-version=7.1-preview.1"
| Resource | resourceType |
resourceId |
|---|---|---|
| Service connection | endpoint |
Service connection id |
| Variable group | variablegroup |
Variable group id |
| Secure file | securefile |
Secure file id |
| Agent queue | queue |
Queue id |
| Environment | environment |
Environment id |
| Repository | repository |
{projectId}.{repositoryId}(providing only the repository ID returns 400) |
allPipelines.authorizedistrue: the UI's Open access(usable by all pipelines), mark ❌. Learn recommends not enabling it and only explicitly authorizing trusted pipelines. Prioritize variable groups containing secrets, service connections, and production environments.- There is no
allPipelinesfield: only the pipelines listed inpipelines[]can use it, mark ✅, and include the number of authorized pipelines. - Microsoft-hosted queues (
pool.isHostedistrue) always returnauthorized: true, andauthorizedOnis the time of the query; this is the default behavior, list it only for reference, do not mark ❌.
5. Approvals and checks for production environment resources (D)
-
Identify production-like resources: service connections, environments, variable groups, and self-hosted queues whose names contain
prod、prd、production、live、正式; show the list to the user and ask the user to add or remove items once. -
For each resource, query the check settings (only the preview version, must include
7.1-preview.1);resourceTypeandresourceId, same as step 4:curl -sS -u ":$AZURE_DEVOPS_PAT" \ "https://dev.azure.com/$AZURE_DEVOPS_ORG/{project}/_apis/pipelines/checks/configurations?resourceType={resourceType}&resourceId={resourceId}&%24expand=settings&api-version=7.1-preview.1" -
Determine: skip checks where
isDisabledistrue.- Approvals (
type.idis8c6f20a7-a545-4486-9777-f762fafe0d4d,type.nameisApproval), or branch control (type.nameisTask Checkandsettings.definitionRef.nameisevaluatebranchProtection,settings.displayNameisBranch control) mark ✅; if neither is present, mark ❌. - Show other checks as
type.name(for example,ExtendsCheckis Required template,ExclusiveLockis Exclusive lock; for the restTask Checkusesettings.displayName)。 - Approvals: show
settings.approvers[].displayNamethe number of approvers andsettings.minRequiredApprovers; branch control: showsettings.inputs.allowedBranches,settings.inputs.ensureProtectionOfBranchis nottrue, note "branch protection not required".
- Approvals (
Examples: "Audit the pipeline security settings of the Contoso project", "Which service connections are still using service principal secrets?", "Which resources are open to all pipelines?"
Present the results
Times are displayed in the user's local time zone; when the time zone cannot be determined, UTC is indicated; the date format is YYYY/MM/DD. Present in order:
-
Summary: the project, the ✅/❌/❓ counts for each type of check, and the checks skipped due to insufficient permissions along with the required scopes.
-
Project settings table: UI setting name, current value, assessment, supporting link.
-
Service connections table: name, type, authentication method, scope, readiness, last used date, open access, approval or branch control (write "—" for non-production environments).
-
Other protected resources table: type, name, open access, number of authorized pipelines, approval or branch control, remarks (for example, "contains secrets", "Microsoft-hosted, for reference only").
-
Remediation steps: grouped by failed items, listing the affected resources and web operations:
- Project settings:Project settings > Pipelines > Settings open the corresponding setting; it is recommended to open them all at once in Organization settings > Pipelines > Settings .
- Service connection authentication method:Project settings > Service connections > select the connection, and according to Use an Azure Resource Manager service connection switch to workload identity federation and narrow the scope to a specific resource group.
- Idle connections: after confirming in Usage history on the same page, disable or remove them (performed by the user).
- Open access: open the resource's Security page, and in Pipeline permissions turn off Open access, and instead add only trusted pipelines. Locations: service connections in Project settings > Pipelines > Service connections > Connections > More actions > Security; variable groups and secure files in Pipelines > Library > Resources > Security; agent pools in Project settings > Pipelines > Agent pools > Pools > Security; environments in Pipelines > Environments > Environments > More actions > Security; repositories in Project settings > Repositories > Repositories > Security。
- Approvals and checks: on the same resource page, add More actions > Approvals and checks in Approvals or Branch control(see Define approvals and checks)。
Finally, note that this report did not modify any settings.