Skip to content

Commit af084e6

Browse files
docs(site): fix permissions.write — executor defaults to System.AccessToken (#890)
1 parent 6e36a86 commit af084e6

3 files changed

Lines changed: 24 additions & 27 deletions

File tree

site/src/content/docs/reference/network.mdx

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,16 @@ network:
109109

110110
## Permissions (ADO Access Tokens)
111111

112-
ADO does not support fine-grained permissions -- there are two access levels: blanket read and blanket write. Tokens are minted from ARM service connections; `System.AccessToken` is never used for agent or executor operations.
112+
ADO does not support fine-grained permissions -- there are two access levels: blanket read and blanket write. ARM service connections are used to mint scoped tokens for the agent (`permissions.read`) and optionally for the executor (`permissions.write`). The Stage 3 executor also uses the pipeline's built-in `$(System.AccessToken)` as its default write token when no `permissions.write` override is configured.
113113

114-
**Exception:** The trigger filter gate step (Setup job) uses `System.AccessToken`
114+
**Note:** The trigger filter gate step (Setup job) uses `System.AccessToken`
115115
for two purposes: (1) self-cancelling the build when filters don't match
116116
(`PATCH` to `_apis/build/builds/{id}`), and (2) fetching PR metadata for
117-
Tier 2 filters (labels, draft status, changed files). This runs in the
118-
Setup job before the agent starts, outside the AWF sandbox. The pipeline
119-
must have "Allow scripts to access the OAuth token" enabled for this to
120-
work. This is a deliberate scoped exception -- the token is not passed to
121-
the agent or executor.
117+
Tier 2 filters (labels, draft status, changed files). The Stage 3 executor
118+
also uses `System.AccessToken` as its default write token. In both cases
119+
the pipeline must have "Allow scripts to access the OAuth token" enabled.
120+
The agent (Stage 1) **never** receives `System.AccessToken` — this trust
121+
boundary ensures the AI agent cannot directly perform write operations.
122122

123123
```yaml
124124
permissions:
@@ -129,26 +129,22 @@ permissions:
129129
### Security Model
130130

131131
- **`permissions.read`**: Mints a read-only ADO-scoped token given to the agent inside the AWF sandbox (Stage 1). The agent can query ADO APIs but cannot write.
132-
- **`permissions.write`**: Mints a write-capable ADO-scoped token used **only** by the executor in Stage 3 (`SafeOutputs` job). This token is never exposed to the agent.
133-
- **Both omitted**: No ADO tokens are passed anywhere. The agent has no ADO API access.
134-
135-
### Compile-Time Validation
136-
137-
If write-requiring safe-outputs (`create-pull-request`, `create-work-item`) are configured but `permissions.write` is missing, compilation fails with a clear error message.
132+
- **`permissions.write`** *(optional)*: Overrides the Stage 3 executor's default `$(System.AccessToken)` with an ARM-minted write token (`SC_WRITE_TOKEN`). Use this for cross-org or cross-project writes, or when you need the action attributed to a named service principal rather than the pipeline's build service identity.
133+
- **Both omitted**: The agent has no ADO API access. Stage 3 still uses `$(System.AccessToken)` for write operations (sufficient for most same-project safe outputs).
138134

139135
### Examples
140136

141137
```yaml
142-
# Agent can read ADO, safe-outputs can write
138+
# Agent can read ADO; executor writes via $(System.AccessToken) (default)
143139
permissions:
144140
read: my-read-sc
145-
write: my-write-sc
146141
147-
# Agent can read ADO, no write safe-outputs needed
142+
# Agent can read ADO; executor writes via named ARM identity
148143
permissions:
149144
read: my-read-sc
145+
write: my-write-sc
150146
151-
# Agent has no ADO access, but safe-outputs can create PRs/work items
147+
# No ADO API read for agent; executor writes via named ARM identity
152148
permissions:
153149
write: my-write-sc
154150
```

site/src/content/docs/reference/template-markers.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,9 +464,10 @@ If `permissions.write` is not configured, this marker is replaced with an empty
464464

465465
## `{{ executor_ado_env }}`
466466

467-
Generates the complete `env:` block (including the `env:` key) for the Stage 3 executor step when `permissions.write` is configured. Sets `SYSTEM_ACCESSTOKEN` to the write service connection token (`SC_WRITE_TOKEN`).
467+
Generates the complete `env:` block (including the `env:` key) for the Stage 3 executor step. This block is **always** emitted — the executor always needs `SYSTEM_ACCESSTOKEN` to authenticate ADO write operations.
468468

469-
If `permissions.write` is not configured, this marker is replaced with an empty string so that no `env:` block is emitted at all. Note: `System.AccessToken` is never used directly -- all ADO tokens come from explicitly configured service connections.
469+
- When `permissions.write` **is** configured: emits `SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)` (the ARM-minted write token from the service connection).
470+
- When `permissions.write` **is not** configured (the default): emits `SYSTEM_ACCESSTOKEN: $(System.AccessToken)` — the pipeline's built-in OAuth token, sufficient for most same-project writes.
470471

471472
## `{{ compiler_version }}`
472473

site/src/content/docs/setup/service-connections.mdx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The compiled pipeline uses the `AzureCLI@2` task to call `az account get-access-
1515

1616
| Connection | Used by | Purpose |
1717
|------------|---------|---------|
18-
| **Write** (required for safe outputs) | Stage 3 — SafeOutputs executor | Create PRs, work items, wiki pages, etc. |
18+
| **Write** (optional override) | Stage 3 — SafeOutputs executor | Override the default `$(System.AccessToken)` for cross-org writes or named-identity attribution |
1919
| **Read** (optional) | Stage 1 — Agent | Query ADO APIs (work items, repos, builds) |
2020

2121
:::tip[Reusing existing connections]
@@ -26,7 +26,7 @@ If your project already has ARM service connections with appropriate ADO permiss
2626

2727
## Create the write service connection
2828

29-
This is the minimum required connection. It powers Stage 3 safe-output execution.
29+
The write service connection overrides the default `$(System.AccessToken)` — use it when you need writes attributed to a named service principal, or for cross-org / cross-project safe outputs where the pipeline's built-in token lacks sufficient scope.
3030

3131
<Steps>
3232
1. Go to your **Azure DevOps Project → Project Settings → Service connections**
@@ -90,8 +90,8 @@ This is the minimum required connection. It powers Stage 3 safe-output execution
9090
Only grant the permissions your workflows actually use. If your agents only create work item comments, you don't need full Contributor access. See [Safe Outputs reference](/ado-aw/reference/safe-outputs/) for what each tool requires.
9191
:::
9292
93-
:::note[Compile-time safety check]
94-
If you configure safe outputs that require write access (e.g. `create-pull-request`, `create-work-item`, `add-pr-comment`) but omit `permissions.write`, compilation will fail with a clear error. This ensures write operations always have an explicitly configured credential.
93+
:::note[Default executor token]
94+
The Stage 3 executor always has write access — it defaults to `$(System.AccessToken)` (the pipeline's built-in OAuth token), which is sufficient for most same-project safe outputs. A `permissions.write` service connection is only needed for cross-org writes, cross-project operations, or when you need the action attributed to a named service principal rather than the pipeline's build service identity.
9595
:::
9696

9797
---
@@ -121,10 +121,10 @@ Separation ensures the Stage 1 agent — which runs untrusted AI-generated code
121121

122122
| Configuration | Agent can read ADO? | Safe outputs can write? |
123123
|---|---|---|
124-
| Both `read` + `write` | ✅ | ✅ |
125-
| Only `read` | ✅ | |
126-
| Only `write` | ❌ | ✅ |
127-
| Neither (default) | ❌ | |
124+
| Both `read` + `write` | ✅ | ✅ (ARM-minted identity) |
125+
| Only `read` | ✅ | ✅ (default `System.AccessToken`) |
126+
| Only `write` | ❌ | ✅ (ARM-minted identity) |
127+
| Neither (default) | ❌ | ✅ (default `System.AccessToken`) |
128128

129129
---
130130

0 commit comments

Comments
 (0)