diff --git a/site/src/content/docs/reference/network.mdx b/site/src/content/docs/reference/network.mdx index fe65b67d..c5a266fd 100644 --- a/site/src/content/docs/reference/network.mdx +++ b/site/src/content/docs/reference/network.mdx @@ -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: @@ -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 ``` diff --git a/site/src/content/docs/reference/template-markers.mdx b/site/src/content/docs/reference/template-markers.mdx index 3599deb1..8a683b47 100644 --- a/site/src/content/docs/reference/template-markers.mdx +++ b/site/src/content/docs/reference/template-markers.mdx @@ -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 }}` diff --git a/site/src/content/docs/setup/service-connections.mdx b/site/src/content/docs/setup/service-connections.mdx index 6743a1c6..136215db 100644 --- a/site/src/content/docs/setup/service-connections.mdx +++ b/site/src/content/docs/setup/service-connections.mdx @@ -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] @@ -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. 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 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. ::: --- @@ -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`) | ---