Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions site/src/content/docs/reference/network.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,16 @@ network:

## Permissions (ADO Access Tokens)

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.
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.

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

```yaml
permissions:
Expand All @@ -129,26 +129,22 @@ permissions:
### Security Model

- **`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.
- **`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.
- **Both omitted**: No ADO tokens are passed anywhere. The agent has no ADO API access.

### Compile-Time Validation

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.
- **`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.
- **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).

### Examples

```yaml
# Agent can read ADO, safe-outputs can write
# Agent can read ADO; executor writes via $(System.AccessToken) (default)
permissions:
read: my-read-sc
write: my-write-sc

# Agent can read ADO, no write safe-outputs needed
# Agent can read ADO; executor writes via named ARM identity
permissions:
read: my-read-sc
write: my-write-sc

# Agent has no ADO access, but safe-outputs can create PRs/work items
# No ADO API read for agent; executor writes via named ARM identity
permissions:
write: my-write-sc
```
5 changes: 3 additions & 2 deletions site/src/content/docs/reference/template-markers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,10 @@ If `permissions.write` is not configured, this marker is replaced with an empty

## `{{ executor_ado_env }}`

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`).
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.

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.
- When `permissions.write` **is** configured: emits `SYSTEM_ACCESSTOKEN: $(SC_WRITE_TOKEN)` (the ARM-minted write token from the service connection).
- 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.

## `{{ compiler_version }}`

Expand Down
16 changes: 8 additions & 8 deletions site/src/content/docs/setup/service-connections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The compiled pipeline uses the `AzureCLI@2` task to call `az account get-access-

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

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

## Create the write service connection

This is the minimum required connection. It powers Stage 3 safe-output execution.
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.

<Steps>
1. Go to your **Azure DevOps Project → Project Settings → Service connections**
Expand Down Expand Up @@ -90,8 +90,8 @@ This is the minimum required connection. It powers Stage 3 safe-output execution
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.
:::

:::note[Compile-time safety check]
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.
:::note[Default executor token]
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.
:::

---
Expand Down Expand Up @@ -121,10 +121,10 @@ Separation ensures the Stage 1 agent — which runs untrusted AI-generated code

| Configuration | Agent can read ADO? | Safe outputs can write? |
|---|---|---|
| Both `read` + `write` | ✅ | ✅ |
| Only `read` | ✅ | |
| Only `write` | ❌ | ✅ |
| Neither (default) | ❌ | |
| Both `read` + `write` | ✅ | ✅ (ARM-minted identity) |
| Only `read` | ✅ | ✅ (default `System.AccessToken`) |
| Only `write` | ❌ | ✅ (ARM-minted identity) |
| Neither (default) | ❌ | ✅ (default `System.AccessToken`) |

---

Expand Down