AzureCLI V1/V2/V3: isolate AZURE_CONFIG_DIR per task invocation#22221
Open
wawanawna wants to merge 7 commits into
Open
AzureCLI V1/V2/V3: isolate AZURE_CONFIG_DIR per task invocation#22221wawanawna wants to merge 7 commits into
wawanawna wants to merge 7 commits into
Conversation
The AzureCLI@1/@2/@3 tasks previously pointed AZURE_CONFIG_DIR at the predictable path Agent.TempDirectory/.azclitask. Because that path is fixed and lives on a shared self-hosted agent, an earlier pipeline step running on the same agent could pre-seed .azclitask/config with an [extension] block (index_url, use_dynamic_install, run_after_dynamic_install) so that the next AzureCLI step's first az invocation would fetch and execute an attacker-controlled wheel under the service-connection identity. Cleanup only happened on the same predictable path, so the attacker dir could be re-seeded across steps. This change introduces an AzureCliConfigDir.ts helper, used by all three task versions: * createPerInvocationAzureConfigDir(agentTempDir) uses fs.mkdtempSync to create a fresh Agent.TempDirectory/.azclitask-XXXXXX directory with a cryptographically random suffix. An attacker can no longer predict the path, so pre-seeded config files at the legacy location are ignored. * removePerInvocationAzureConfigDir(path) performs a never-throwing cleanup (rmRF + unset env var) so the per-invocation directory is removed even when the script body throws and so cleanup errors do not mask the original failure. For V2 and V3, scriptType.cleanUp() in the finally block is now wrapped in its own try/catch so a cleanup error there cannot prevent the new config-dir cleanup from running. Behavior change for users: cross-step persistence of az extension add or az config set between AzureCLI steps in the same job is no longer guaranteed (each step now gets a fresh, isolated config dir). The pre-existing useGlobalConfig: true input remains the supported escape hatch for pipelines that rely on persistence. Tests: adds Tests/L0ConfigDirIsolation.ts to each task with eight L0 cases covering directory creation, uniqueness across invocations, mutual isolation, attacker-pre-seeding resistance, input validation, cleanup of populated directories, null/empty no-op, and missing-path no-op. Full L0 suite passes per task (V1 21, V2 47, V3 59).
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
CI 'Check for downgrading tasks' requires task version > master: - AzureCLIV1: 1.274.0 -> 1.275.0 (minor < sprint) - AzureCLIV2: 2.275.4 -> 2.275.6 (== master patch) - AzureCLIV3: 3.275.8 -> 3.275.10 (== master patch) Node24 variants auto-bumped to <Default>+1 by BuildConfigGen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
createPerInvocationAzureConfigDir now sets process.env.AZURE_CONFIG_DIR to the new directory (matching the unset already done by removePerInvocationAzureConfigDir on cleanup). Callers no longer need to remember to mirror the env assignment after the mkdtempSync. Behavior is unchanged: identical env-var lifetime as before (set in beforeExecution, unset in finally), but the pair is now encapsulated in one module so it cannot drift. The helper stays per-task (one copy in each of V1/V2/V3) to match the repo convention of self-contained tasks. Added L0 test asserting the env var is set after the call. Cleanup in the first describe's afterEach unsets the var so tests stay isolated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c6ac5c1 to
6626762
Compare
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens AzureCLI@1/@2/@3 by moving AZURE_CONFIG_DIR from a predictable shared temp path to a per-invocation temp directory, reducing cross-step config pre-seeding risk on shared agents.
Changes:
- Adds duplicated Azure CLI config-dir helper modules and L0 tests for V1/V2/V3.
- Updates task execution cleanup to remove the per-invocation config directory.
- Bumps task versions and generated build mappings for default and Node24 variants.
Reviewed changes
Copilot reviewed 57 out of 57 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
Tasks/AzureCLIV1/azureclitask.ts |
Uses isolated config dir and cleanup in V1. |
Tasks/AzureCLIV1/src/AzureCliConfigDir.ts |
Adds V1 helper for creating/removing isolated AZURE_CONFIG_DIR. |
Tasks/AzureCLIV1/Tests/L0.ts |
Wires config-dir isolation tests into V1 L0 suite. |
Tasks/AzureCLIV1/Tests/L0ConfigDirIsolation.ts |
Adds V1 helper unit coverage. |
Tasks/AzureCLIV1/task.json |
Bumps V1 task version. |
Tasks/AzureCLIV1/task.loc.json |
Mirrors V1 version bump. |
Tasks/AzureCLIV2/azureclitask.ts |
Uses isolated config dir and cleanup in V2. |
Tasks/AzureCLIV2/src/AzureCliConfigDir.ts |
Adds V2 helper for creating/removing isolated AZURE_CONFIG_DIR. |
Tasks/AzureCLIV2/Tests/L0.ts |
Wires config-dir isolation tests into V2 L0 suite. |
Tasks/AzureCLIV2/Tests/L0ConfigDirIsolation.ts |
Adds V2 helper unit coverage. |
Tasks/AzureCLIV2/task.json |
Bumps V2 task version. |
Tasks/AzureCLIV2/task.loc.json |
Mirrors V2 version bump. |
Tasks/AzureCLIV3/azureclitask.ts |
Uses isolated config dir and cleanup in V3. |
Tasks/AzureCLIV3/src/AzureCliConfigDir.ts |
Adds V3 helper for creating/removing isolated AZURE_CONFIG_DIR. |
Tasks/AzureCLIV3/Tests/L0.ts |
Wires config-dir isolation tests into V3 L0 suite. |
Tasks/AzureCLIV3/Tests/L0ConfigDirIsolation.ts |
Adds V3 helper unit coverage. |
Tasks/AzureCLIV3/task.json |
Bumps V3 task version. |
Tasks/AzureCLIV3/task.loc.json |
Mirrors V3 version bump. |
_generated/AzureCLIV1/azureclitask.ts |
Mirrors V1 runtime changes. |
_generated/AzureCLIV1/src/AzureCliConfigDir.ts |
Mirrors V1 helper. |
_generated/AzureCLIV1/Tests/L0.ts |
Mirrors V1 test wiring. |
_generated/AzureCLIV1/Tests/L0ConfigDirIsolation.ts |
Mirrors V1 helper tests. |
_generated/AzureCLIV1/task.json |
Updates generated V1 version mapping. |
_generated/AzureCLIV1/task.loc.json |
Updates generated V1 localized metadata. |
_generated/AzureCLIV1.versionmap.txt |
Updates generated V1 version map. |
_generated/AzureCLIV1_Node24/azureclitask.ts |
Mirrors V1 Node24 runtime changes. |
_generated/AzureCLIV1_Node24/src/AzureCliConfigDir.ts |
Mirrors V1 Node24 helper. |
_generated/AzureCLIV1_Node24/Tests/L0.ts |
Mirrors V1 Node24 test wiring. |
_generated/AzureCLIV1_Node24/Tests/L0ConfigDirIsolation.ts |
Mirrors V1 Node24 helper tests. |
_generated/AzureCLIV1_Node24/task.json |
Updates V1 Node24 task version. |
_generated/AzureCLIV1_Node24/task.loc.json |
Updates V1 Node24 localized metadata. |
_generated/AzureCLIV2/azureclitask.ts |
Mirrors V2 runtime changes. |
_generated/AzureCLIV2/src/AzureCliConfigDir.ts |
Mirrors V2 helper. |
_generated/AzureCLIV2/Tests/L0.ts |
Mirrors V2 test wiring. |
_generated/AzureCLIV2/Tests/L0ConfigDirIsolation.ts |
Mirrors V2 helper tests. |
_generated/AzureCLIV2/task.json |
Updates generated V2 version mapping. |
_generated/AzureCLIV2/task.loc.json |
Updates generated V2 localized metadata. |
_generated/AzureCLIV2.versionmap.txt |
Updates generated V2 version map. |
_generated/AzureCLIV2_Node24/azureclitask.ts |
Mirrors V2 Node24 runtime changes. |
_generated/AzureCLIV2_Node24/src/AzureCliConfigDir.ts |
Mirrors V2 Node24 helper. |
_generated/AzureCLIV2_Node24/Tests/L0.ts |
Mirrors V2 Node24 test wiring. |
_generated/AzureCLIV2_Node24/Tests/L0ConfigDirIsolation.ts |
Mirrors V2 Node24 helper tests. |
_generated/AzureCLIV2_Node24/task.json |
Updates V2 Node24 task version. |
_generated/AzureCLIV2_Node24/task.loc.json |
Updates V2 Node24 localized metadata. |
_generated/AzureCLIV3/azureclitask.ts |
Mirrors V3 runtime changes. |
_generated/AzureCLIV3/src/AzureCliConfigDir.ts |
Mirrors V3 helper. |
_generated/AzureCLIV3/Tests/L0.ts |
Mirrors V3 test wiring. |
_generated/AzureCLIV3/Tests/L0ConfigDirIsolation.ts |
Mirrors V3 helper tests. |
_generated/AzureCLIV3/task.json |
Updates generated V3 version mapping. |
_generated/AzureCLIV3/task.loc.json |
Updates generated V3 localized metadata. |
_generated/AzureCLIV3.versionmap.txt |
Updates generated V3 version map. |
_generated/AzureCLIV3_Node24/azureclitask.ts |
Mirrors V3 Node24 runtime changes. |
_generated/AzureCLIV3_Node24/src/AzureCliConfigDir.ts |
Mirrors V3 Node24 helper. |
_generated/AzureCLIV3_Node24/Tests/L0.ts |
Mirrors V3 Node24 test wiring. |
_generated/AzureCLIV3_Node24/Tests/L0ConfigDirIsolation.ts |
Mirrors V3 Node24 helper tests. |
_generated/AzureCLIV3_Node24/task.json |
Updates V3 Node24 task version. |
_generated/AzureCLIV3_Node24/task.loc.json |
Updates V3 Node24 localized metadata. |
Commit 6626762 paired the env-var unset into removePerInvocationAzureConfigDir, but the cleanup call ran before logoutAzure() (which invokes `az account clear`, and on V3 also before `az devops configure --defaults`). With AZURE_CONFIG_DIR already unset, those `az` commands fell back to ~/.azure and mutated the agent's global Azure CLI profile -- on self-hosted agents this could clear unrelated cached logins. Move the dir removal to after all `az` cleanup commands so they keep seeing the per-invocation profile. Addresses copilot-pull-request-reviewer[bot] review comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Comment on lines
20
to
24
| "version": { | ||
| "Major": 1, | ||
| "Minor": 274, | ||
| "Minor": 275, | ||
| "Patch": 0 | ||
| }, |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
…p errors - createPerInvocationAzureConfigDir now wraps mkdtempSync in a try/catch and rethrows with an actionable message pointing at Agent.TempDirectory and the useGlobalConfig escape hatch. Behaviour is unchanged (still fails the task on mkdtemp failure); previously the raw EACCES/ENOENT was harder to diagnose. - V2/V3: scriptType.cleanUp() failures are now logged via tl.warning instead of tl.debug so they show up in the build summary without requiring system.debug=true. Control flow is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Author
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
…r received HTML instead of JSON while polling child build status) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
Author
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
Contributor
Author
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
AzureCLI@1/@2/@3 used to point
AZURE_CONFIG_DIRat a fixed path under the agent temp directory (Agent.TempDirectory/.azclitask).On a shared self-hosted agent, an earlier pipeline step could pre-seed
.azclitask/configwith an attacker-controlled[extension]block —index_url,use_dynamic_install,run_after_dynamic_install— so that the next AzureCLI step's firstazcall would fetch and execute an attacker-controlled wheel under the service-connection identity.The end-of-task cleanup also operated on the same fixed path, so the attacker dir could be re-seeded across steps.
This PR moves all three task versions to a per-invocation, unpredictable config dir.
How
createPerInvocationAzureConfigDir(agentTempDir)— usesfs.mkdtempSyncto createAgent.TempDirectory/.azclitask-XXXXXXwith a cryptographically random suffix (libuv tempchars, filesystem-safe on every supported OS). Available since Node 5.10 — well below every Node version the task handlers ship with.removePerInvocationAzureConfigDir(path)— never-throws cleanup (tl.rmRF+delete process.env['AZURE_CONFIG_DIR']) so cleanup errors can't mask the original task failure.For V2 and V3,
scriptType.cleanUp()in thefinallyblock is now wrapped in its own try/catch so a cleanup error there can't prevent the config-dir cleanup from running.Behavior change for users
Cross-step persistence of
az extension add/az config setbetween AzureCLI steps in the same job is no longer guaranteed — each step now gets a fresh, isolated config dir. The pre-existinguseGlobalConfig: trueinput remains the supported escape hatch for pipelines that rely on persistence;Tests
Adds
Tests/L0ConfigDirIsolation.tsto each task — 8 L0 cases covering:configeven if attacker has pre-populated the legacy pathagentTempDirAZURE_CONFIG_DIRfinally)Full L0 suite passes per task: V1 21 passing, V2 47 passing, V3 59 passing.