Skip to content

Commit 41c8b27

Browse files
committed
Add section on System.AccessToken access model and security implications for Advanced Security APIs
1 parent 820c3c8 commit 41c8b27

1 file changed

Lines changed: 144 additions & 39 deletions

File tree

docs/ado-rest-api-auth-without-pat.md

Lines changed: 144 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ Every Azure Pipelines job automatically gets an OAuth token via `$(System.Access
6565
### ⚠️ Advanced Security Impact
6666
As of **Sprint 269**, build service identities can no longer call Advanced Security APIs. A temporary rollback is in effect **until April 15, 2026**, after which `System.AccessToken` **will not work** for Advanced Security REST API calls.
6767

68+
### Important: Access Model for System.AccessToken
69+
70+
- **Script steps** (PowerShell, Bash inline): require explicit `env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)` mapping or the "Allow scripts to access the OAuth token" job setting enabled
71+
- **Pipeline tasks/extensions**: can access the token via the Task SDK (`SYSTEMVSSCONNECTION` endpoint) without any user opt-in — see [Section 14](#14-why-microsoft-removed-build-identity-access--security-analysis) for security implications
72+
6873
### Usage
6974
```yaml
7075
steps:
@@ -572,6 +577,7 @@ steps:
572577
**Task acquires token internally**:
573578
```typescript
574579
// Inside the custom task
580+
const tenantId = tl.getInput('tenantId', true)!;
575581
const body = new URLSearchParams({
576582
client_id: tl.getInput('clientId', true)!,
577583
scope: 'https://app.vssps.visualstudio.com/.default',
@@ -855,7 +861,7 @@ steps:
855861
│ │
856862
│ Updated Decorator (one-time change) │
857863
│ └── Replace System.AccessToken usage with AzureCLI@3 │
858-
│ └── Reference: azureDevOpsServiceConnection: advsec-gate │
864+
│ └── Reference: azureDevOpsServiceConnection: advsec-gate-sc
859865
│ └── Republish extension │
860866
│ │
861867
│ Total cost: ~$6/month │
@@ -982,71 +988,170 @@ foreach ($project in $allProjects) {
982988

983989
## 14. Why Microsoft Removed Build Identity Access — Security Analysis
984990

991+
### Impact: 🔴 Critical
992+
993+
This section explains the security rationale behind Microsoft's Sprint 269 decision to block build service identities from Advanced Security APIs. The findings below are based on verified technical analysis of the Azure Pipelines Task SDK, Microsoft documentation, and actual marketplace extension source code.
994+
985995
### The Security Risk (Confirmed)
986996

987-
**Your intuition is correct.** Prior to Sprint 269, any pipeline running with `System.AccessToken` could read Advanced Security alerts because the Build Service identity had **implicit API access by default**. This created a real security risk:
997+
Prior to Sprint 269, any pipeline running with `System.AccessToken` could read Advanced Security alerts because the Build Service identity had **implicit API access by default**. This created a critical security risk:
988998

989999
```
990-
Risk Chain:
991-
Any pipeline task/extension (including marketplace)
992-
Accesses System.AccessToken (tasks get it automatically)
993-
Authenticates as Build Service identity
1000+
Attack Chain:
1001+
Any installed pipeline task/extension (including marketplace)
1002+
Calls tl.getEndpointAuthorizationParameter('SYSTEMVSSCONNECTION', 'AccessToken', false)
1003+
Receives the Build Service identity OAuth token (no user opt-in required)
9941004
→ Calls Advanced Security API (allowed by default pre-Sprint 269)
995-
→ Reads all security alerts for the project/repo
996-
→ Can exfiltrate vulnerability data
1005+
→ Reads all security alerts (vulnerabilities, secrets, dependencies)
1006+
→ Can exfiltrate sensitive vulnerability data to external services
9971007
```
9981008

999-
### Key Facts from Microsoft Documentation
1009+
### The Technical Mechanism — SYSTEMVSSCONNECTION
1010+
1011+
There are **three ways** a pipeline component can access the Build Service OAuth token. Understanding the distinction is critical:
1012+
1013+
| Mechanism | Who Can Use It | User Opt-in Required? | Severity |
1014+
|-----------|---------------|----------------------|----------|
1015+
| `tl.getEndpointAuthorizationParameter('SYSTEMVSSCONNECTION', 'AccessToken', false)` | **Any pipeline task** via the Task SDK |**No** — built-in system endpoint, always available | 🔴 Critical |
1016+
| `tl.getVariable('System.AccessToken')` | **Any pipeline task** via the Task SDK |**No** — pipeline variables are accessible to tasks | 🔴 Critical |
1017+
| `process.env['SYSTEM_ACCESSTOKEN']` | **Scripts** (inline bash/PS) |**Yes** — requires `env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)` or "Allow scripts to access OAuth token" | 🟡 Medium |
1018+
1019+
**Key finding**: `SYSTEMVSSCONNECTION` is a **built-in system service endpoint** that Azure Pipelines exposes to every task in every job. It represents the Build Service identity. Any pipeline task — including marketplace extensions — can call `tl.getEndpointAuthorizationParameter('SYSTEMVSSCONNECTION', 'AccessToken', false)` to obtain the OAuth token **without any user configuration or opt-in**.
1020+
1021+
The "Allow scripts to access the OAuth token" setting **only applies to script steps** (inline PowerShell, Bash, CMD). It does **NOT** restrict task-level access via the Task SDK. This distinction is often misunderstood.
10001022

1001-
1. **Build Service had default access**: The `Project Collection Build Service` and project-scoped Build Service identities were allowed to call Advanced Security APIs by default — no explicit permission grant was needed.
1023+
### What This Means in Practice
10021024

1003-
2. **Tasks vs Scripts — important distinction**:
1004-
- **Scripts** (PowerShell, Bash inline): require explicit `env: SYSTEM_ACCESSTOKEN: $(System.AccessToken)` mapping
1005-
- **Pipeline Tasks/Extensions**: can access the job access token as part of their normal operation via the Task SDK, without the user explicitly mapping it
1025+
A marketplace extension does NOT need to declare any special permissions or service connection inputs to access the Build Service token. The task code simply needs:
1026+
1027+
```typescript
1028+
import * as tl from 'azure-pipelines-task-lib/task';
1029+
1030+
// No user opt-in, no service connection input, no special permission
1031+
const token = tl.getEndpointAuthorizationParameter('SYSTEMVSSCONNECTION', 'AccessToken', false);
1032+
1033+
// This token authenticates as the Build Service identity
1034+
// Pre-Sprint 269: could call Advanced Security APIs with this token
1035+
const response = await fetch(
1036+
`https://advsec.dev.azure.com/{org}/{project}/_apis/alert/repositories/{repoId}/alerts?api-version=7.2-preview.1`,
1037+
{ headers: { 'Authorization': `Bearer ${token}` } }
1038+
);
1039+
// → Returns all security alerts: code vulnerabilities, exposed secrets, dependency CVEs
1040+
```
1041+
1042+
**Verified by source code analysis**: Popular marketplace extensions (e.g., token replacement, code analysis, deployment tools) typically do NOT access `SYSTEMVSSCONNECTION` or call ADO REST APIs beyond their stated purpose. However, **nothing in the platform prevents them from doing so** — there is no sandboxing, no API allowlist per extension, and no runtime permission gate.
1043+
1044+
### Three Compounding Factors
1045+
1046+
**Factor 1: Shared Identity — No Pipeline Isolation**
1047+
1048+
The Build Service identity (`Project Collection Build Service` or `{Project} Build Service`) is **shared across ALL pipelines** in a project (or the entire collection if not restricted). Every task in every pipeline gets the same identity with the same permissions.
1049+
1050+
> From [Microsoft Learn](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/access-tokens): *"The permissions of this token are based on the Project Build Service identity, meaning all job access tokens in a project have identical permissions."*
1051+
1052+
**Factor 2: Advanced Security API Access Was Not Explicitly Gated**
1053+
1054+
Prior to Sprint 269, Build Service identities were **not blocked at the API level** from calling Advanced Security endpoints. The [Sprint 269 release notes](https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-269-update) confirm this was the change: *"Advanced Security REST APIs no longer accept build service identities."* The [rollback blog](https://devblogs.microsoft.com/devops/temporary-rollback-build-identities-can-access-advanced-security-read-alerts-again/) further states the restriction was *"a security improvement"* that was rolled back because customers relied on this access for automation. This means pipelines using `System.AccessToken` could call the Advanced Security API without any administrator having explicitly granted `Advanced Security: Read alerts` to the Build Service.
1055+
1056+
**Factor 3: No Extension Sandboxing**
1057+
1058+
Azure DevOps does not sandbox pipeline task execution. A task runs as a Node.js process on the agent with full access to:
1059+
- The `SYSTEMVSSCONNECTION` token
1060+
- The agent's file system (build sources, artifacts, secrets on disk)
1061+
- Outbound network access (can exfiltrate data)
1062+
- All environment variables set by previous tasks
1063+
1064+
### The Risk: Supply-Chain Attacks on Marketplace Extensions
1065+
1066+
The combination of these three factors creates a supply-chain attack vector:
1067+
1068+
```
1069+
1. Attacker publishes (or compromises) a popular marketplace extension
1070+
2. Extension update adds code to read SYSTEMVSSCONNECTION token
1071+
3. Extension calls Advanced Security API to harvest vulnerability data
1072+
4. Extension exfiltrates: which repos have unpatched CVEs, exposed secrets, code vulnerabilities
1073+
5. Attacker uses this intelligence to target the organization's weakest points
1074+
```
10061075

1007-
3. **Shared identity problem**: The Build Service identity is **shared across ALL pipelines in a project** (or the entire collection if not restricted). There's no per-pipeline isolation. Any task in any pipeline gets the same identity.
1076+
This is not theoretical — Microsoft's own pipeline security guidance explicitly warns:
10081077

1009-
4. **Microsoft's stated reasoning** (Sprint 269 release notes):
1010-
> *"This change prevents pipeline-based automation from accessing or modifying security alert data using build service accounts, reducing the risk of unintended alert state changes during CI/CD runs."*
1078+
> From [Microsoft Learn - Secure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/security/misc): *"If a malicious actor gains pipeline access in one project, and Build Service identities are insufficiently scoped, they could affect other projects' resources — escalating an initially limited breach into a wider compromise."*
10111079
1012-
5. **The fix**: Microsoft is requiring a **named service principal** with explicit `Advanced Security: Read alerts` permission, which provides:
1013-
- Explicit, auditable identity (not a shared build account)
1014-
- Can be scoped narrowly
1015-
- Uses Entra tokens (short-lived, conditional access)
1016-
- Per-pipeline access control via service connections
1080+
### Microsoft's Stated Reasoning (Sprint 269)
10171081

1018-
### Is a Simple Extension a Risk?
1082+
From the [Sprint 269 release notes](https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-269-update):
10191083

1020-
**Yes.** A marketplace extension (or any custom task) installed in the organization:
1021-
- Runs with the job's access token automatically (tasks access it via the Task SDK)
1022-
- Does NOT need "Allow scripts to access the OAuth token" — that setting only applies to script steps, not task steps
1023-
- Could call any API the Build Service identity has access to
1024-
- Before Sprint 269, that included Advanced Security APIs
1084+
> *"Advanced Security REST APIs no longer accept build service identities (such as `Project Collection Build Service`) as callers. This change prevents pipeline-based automation from accessing or modifying security alert data using build service accounts, reducing the risk of unintended alert state changes during CI/CD runs."*
10251085
1026-
**This is precisely why Microsoft made the Sprint 269 change** — to ensure that only explicitly authorized service principals (with audit trails and conditional access) can read security alert data, not any pipeline task running under the broadly-shared Build Service identity.
1086+
From the [temporary rollback blog post](https://devblogs.microsoft.com/devops/temporary-rollback-build-identities-can-access-advanced-security-read-alerts-again/):
1087+
1088+
> *"We restricted API access for build identities as a security improvement but failed to provide an early notice for customers that relied upon this for various automations."*
1089+
1090+
### How a Service Principal Fixes This
1091+
1092+
Moving to a dedicated Service Principal with an Azure DevOps Service Connection resolves all three compounding factors:
1093+
1094+
| Factor | Build Service (old) | Service Principal (new) |
1095+
|--------|-------------------|----------------------|
1096+
| **Identity isolation** | Shared across all pipelines in the project | Only the pipeline/task referencing the SC gets the token |
1097+
| **Permission model** | Implicit — Build Service had default API access | Explicit — SP must be granted `Advanced Security: Read alerts` specifically |
1098+
| **Audit trail** | Generic "Build Service" in logs | Named SP identity with Entra audit logs |
1099+
| **Token access** | Any task gets it via `SYSTEMVSSCONNECTION` | Only the `AzureCLI@3` step (or task with SC input) referencing the specific SC |
1100+
| **Conditional Access** | Not supported | ✅ Entra Conditional Access policies apply |
1101+
| **Credential lifetime** | Job duration (up to 48h) | 1 hour (Entra token), auto-refreshed |
1102+
| **Extension risk** | Any installed extension can read the token | Extensions cannot access the SP token unless they reference the SC by name |
10271103

10281104
---
10291105

10301106
## 15. References
10311107

1108+
### Official Microsoft Documentation
1109+
10321110
| Resource | URL |
10331111
|----------|-----|
1034-
| Reducing PAT Usage Across Azure DevOps | https://devblogs.microsoft.com/devops/reducing-pat-usage-across-azure-devops/ |
1035-
| Temporary Rollback: Build Identities & Advanced Security | https://devblogs.microsoft.com/devops/temporary-rollback-build-identities-can-access-advanced-security-read-alerts-again/ |
1036-
| Service Principals & Managed Identities in ADO | https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity |
10371112
| Authenticate with Entra ID | https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/entra |
1113+
| Service Principals & Managed Identities in ADO | https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity |
1114+
| Pipeline Job Access Tokens (System.AccessToken) | https://learn.microsoft.com/en-us/azure/devops/pipelines/process/access-tokens |
10381115
| Azure DevOps Service Connection (Workload Identity) | https://learn.microsoft.com/en-us/azure/devops/pipelines/library/add-devops-entra-service-connection |
1039-
| PAT-less Authentication from Pipeline Tasks (Roadmap) | https://learn.microsoft.com/en-us/azure/devops/release-notes/roadmap/2025/new-service-connection |
1040-
| Pipeline Job Access Tokens | https://learn.microsoft.com/en-us/azure/devops/pipelines/process/access-tokens |
1116+
| Service Connections Overview | https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints |
10411117
| Entra Tokens via Azure CLI | https://learn.microsoft.com/en-us/azure/devops/cli/entra-tokens |
1042-
| No New Azure DevOps OAuth Apps (April 2025) | https://devblogs.microsoft.com/devops/no-new-azure-devops-oauth-apps/ |
1043-
| Configure Advanced Security Status Checks | https://learn.microsoft.com/en-us/azure/devops/repos/security/configure-github-advanced-security-features |
1118+
| Advanced Security Permissions | https://learn.microsoft.com/en-us/azure/devops/repos/security/github-advanced-security-permissions |
1119+
| Configure Advanced Security Features & Status Checks | https://learn.microsoft.com/en-us/azure/devops/repos/security/configure-github-advanced-security-features |
10441120
| Author a Pipeline Decorator | https://learn.microsoft.com/en-us/azure/devops/extend/develop/add-pipeline-decorator |
10451121
| Pipeline Decorator Context | https://learn.microsoft.com/en-us/azure/devops/extend/develop/pipeline-decorator-context |
1046-
| Advanced Security Permissions | https://learn.microsoft.com/en-us/azure/devops/repos/security/github-advanced-security-permissions |
1047-
| Sprint 269 Release Notes (Build Identity Restriction) | https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-269-update |
1048-
| Sprint 271 Release Notes (Status Checks) | https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-271-update |
1049-
| Pipeline Security Best Practices | https://learn.microsoft.com/en-us/azure/devops/pipelines/security/misc |
1122+
| Add a Custom Build Task in an Extension | https://learn.microsoft.com/en-us/azure/devops/extend/develop/add-build-task |
1123+
| Pipeline Security — Secure Agents, Projects, and Containers | https://learn.microsoft.com/en-us/azure/devops/pipelines/security/misc |
1124+
| Azure Pipelines Task SDK (azure-pipelines-task-lib) | https://github.com/microsoft/azure-pipelines-task-lib |
1125+
| Advanced Security Alerts REST API Reference | https://learn.microsoft.com/en-us/rest/api/azure/devops/advancedsecurity/alerts/list |
1126+
| Buy Basic Access & Manage Users (Licensing) | https://learn.microsoft.com/en-us/azure/devops/organizations/billing/buy-basic-access-add-users |
1127+
| Entra Token Lifetimes | https://learn.microsoft.com/en-us/entra/identity-platform/configurable-token-lifetimes |
1128+
| Managed Identity Token Caching | https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/managed-identities-faq |
1129+
| Entra OAuth for Azure DevOps (Migration from ADO OAuth) | https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/entra-oauth |
1130+
| AzureCLI@3 Task Reference (connectionType: azureDevOps) | https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/azure-cli-v3 |
1131+
1132+
### Release Notes & Roadmap
1133+
1134+
| Resource | URL |
1135+
|----------|-----|
1136+
| Sprint 269 Release Notes — Build Identity Restriction | https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-269-update |
1137+
| Sprint 271 Release Notes — Status Checks | https://learn.microsoft.com/en-us/azure/devops/release-notes/2026/ghazdo/sprint-271-update |
1138+
| PAT-less Authentication from Pipeline Tasks (Roadmap) | https://learn.microsoft.com/en-us/azure/devops/release-notes/roadmap/2025/new-service-connection |
1139+
| Sprint 254 Update — No New OAuth Apps | https://learn.microsoft.com/en-us/azure/devops/release-notes/2025/general/sprint-254-update |
1140+
1141+
### Blog Posts
1142+
1143+
| Resource | URL |
1144+
|----------|-----|
1145+
| Reducing PAT Usage Across Azure DevOps | https://devblogs.microsoft.com/devops/reducing-pat-usage-across-azure-devops/ |
1146+
| Temporary Rollback: Build Identities & Advanced Security | https://devblogs.microsoft.com/devops/temporary-rollback-build-identities-can-access-advanced-security-read-alerts-again/ |
1147+
| No New Azure DevOps OAuth Apps (April 2025) | https://devblogs.microsoft.com/devops/no-new-azure-devops-oauth-apps/ |
1148+
1149+
### Code Samples & SDK References
1150+
1151+
| Resource | URL |
1152+
|----------|-----|
1153+
| Azure DevOps Auth Samples (Service Principals) | https://github.com/microsoft/azure-devops-auth-samples/tree/master/ServicePrincipalsSamples |
1154+
| Azure Pipelines Decorator Samples | https://github.com/n3wt0n/AzurePipelinesDecoratorSamples |
10501155

10511156
---
10521157

0 commit comments

Comments
 (0)