From 8be42d00bdfc31c63c16a0242ba07b65b6410a11 Mon Sep 17 00:00:00 2001 From: alexweininger Date: Wed, 1 Jul 2026 11:34:43 -0400 Subject: [PATCH 1/2] Honor VS Code proxy settings in storage data-plane clients The @azure/storage-blob, -file-share, and -queue SDKs do not read VS Code's http.proxy / http.noProxy settings on their own, so data-plane operations fail on corporate networks that require an explicit proxy for outbound traffic. Add a small withProxyOptions() helper that resolves proxy configuration via the shared getProxySettings() from @microsoft/vscode-azext-azureutils and merges it into each service client's StoragePipelineOptions. Wire it into the Blob, File, and Queue clients in StorageAccountTreeItem plus the Blob client in BlobContainerFS. Table (@azure/data-tables) does not expose proxyOptions and is deferred. proxyStrictSSL:false is likewise not expressible through proxyOptions. Related to #1527 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package.json | 2 +- src/BlobContainerFS.ts | 3 ++- src/tree/StorageAccountTreeItem.ts | 13 +++++++------ src/utils/proxyUtils.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 src/utils/proxyUtils.ts diff --git a/package.json b/package.json index 6a52cb98..7ed868aa 100644 --- a/package.json +++ b/package.json @@ -821,7 +821,7 @@ "@azure/storage-file-datalake": "^12.1.1", "@azure/storage-file-share": "^12.25.0-beta.1", "@azure/storage-queue": "^12.7.0", - "@microsoft/vscode-azext-azureutils": "^4.0.1", + "@microsoft/vscode-azext-azureutils": "^4.3.0", "@microsoft/vscode-azext-utils": "^4.0.4", "@microsoft/vscode-azureresources-api": "^3.1.0", "mime": "^2.4.4", diff --git a/src/BlobContainerFS.ts b/src/BlobContainerFS.ts index f591d3e5..e178d295 100644 --- a/src/BlobContainerFS.ts +++ b/src/BlobContainerFS.ts @@ -23,6 +23,7 @@ import { createStorageClient } from './utils/azureClients'; import { BlobPathUtils } from './utils/blobPathUtils'; import { createBlockBlobClient } from './utils/blobUtils'; import { localize } from './utils/localize'; +import { withProxyOptions } from './utils/proxyUtils'; import { nonNullValue } from "./utils/nonNull"; import { StorageAccountKeyWrapper } from './utils/storageWrappers'; @@ -136,7 +137,7 @@ export class BlobContainerFS implements vscode.FileSystemProvider { throw Error("Unable to get key credential."); } - const serviceClient = new BlobServiceClient(blobEndpoint, credential); + const serviceClient = new BlobServiceClient(blobEndpoint, credential, withProxyOptions(blobEndpoint)); const containerClient = serviceClient.getContainerClient(containerName); const blobClient = containerClient.getBlobClient(blobPath); diff --git a/src/tree/StorageAccountTreeItem.ts b/src/tree/StorageAccountTreeItem.ts index 40585a5b..c40ae387 100644 --- a/src/tree/StorageAccountTreeItem.ts +++ b/src/tree/StorageAccountTreeItem.ts @@ -25,6 +25,7 @@ import { createActivityContext } from '../utils/activityUtils'; import { createStorageClient } from '../utils/azureClients'; import { localize } from '../utils/localize'; import { nonNullProp } from '../utils/nonNull'; +import { withProxyOptions } from '../utils/proxyUtils'; import { StorageAccountKeyWrapper, StorageAccountWrapper } from '../utils/storageWrappers'; import { IStorageRoot } from './IStorageRoot'; import { IStorageTreeItem } from './IStorageTreeItem'; @@ -289,7 +290,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialBlob(this.storageAccount.name, ((await this.getKey()).value)); - client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential); + client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -298,7 +299,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), token); + client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'))); await client.getProperties(); // Trigger a request to validate the token } @@ -309,7 +310,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialFileShare(this.storageAccount.name, ((await this.getKey()).value)); - client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential); + client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'file'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -317,7 +318,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT } if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), token, { fileRequestIntent: 'backup' }); + client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), { fileRequestIntent: 'backup' })); await client.getProperties(); // Trigger a request to validate the token } @@ -328,7 +329,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialQueue(this.storageAccount.name, ((await this.getKey()).value)); - client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential); + client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -336,7 +337,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT } if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), token); + client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'))); await client.getProperties(); // Trigger a request to validate the token } diff --git a/src/utils/proxyUtils.ts b/src/utils/proxyUtils.ts new file mode 100644 index 00000000..3e6c5305 --- /dev/null +++ b/src/utils/proxyUtils.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getProxySettings, type ProxySettings } from '@microsoft/vscode-azext-azureutils'; + +/** + * Merges VS Code's proxy configuration (`http.proxy` / `http.noProxy`, with the standard proxy + * environment variables as a fallback) into the pipeline options used to construct a storage + * data-plane service client, so requests route through a corporate proxy. + * + * The storage SDKs (`@azure/storage-blob`, `-file-share`, `-queue`) do not consult VS Code's + * `http.*` settings on their own, so operations fail on networks that require an explicit proxy. + * Returns `options` unchanged when no proxy applies to `endpointUrl` (for example when the host is + * matched by `http.noProxy`/`NO_PROXY`, or proxy support is turned off). + */ +export function withProxyOptions(endpointUrl: string): { proxyOptions?: ProxySettings } | undefined; +export function withProxyOptions(endpointUrl: string, options: T): (T & { proxyOptions?: ProxySettings }) | undefined; +export function withProxyOptions(endpointUrl: string, options?: T): (T & { proxyOptions?: ProxySettings }) | { proxyOptions?: ProxySettings } | undefined { + const proxyOptions = getProxySettings(endpointUrl); + if (!proxyOptions) { + return options; + } + return options ? { ...options, proxyOptions } : { proxyOptions }; +} From d3eeae4f7a05de10f21e694408522a824452e709 Mon Sep 17 00:00:00 2001 From: alexweininger Date: Wed, 1 Jul 2026 13:26:54 -0400 Subject: [PATCH 2/2] Also honor proxy in Data Lake (HNS) data-plane clients getDataLakeClients constructs DataLakeServiceClient and DataLakePathClient, which is the code path for hierarchical-namespace (Data Lake Gen2) accounts. These were left unproxied while the sibling blob client was wired, so file-system operations on HNS accounts would still fail behind a corporate proxy. Wire both through withProxyOptions, mirroring the blob path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/BlobContainerFS.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BlobContainerFS.ts b/src/BlobContainerFS.ts index e178d295..66aad25b 100644 --- a/src/BlobContainerFS.ts +++ b/src/BlobContainerFS.ts @@ -161,12 +161,12 @@ export class BlobContainerFS implements vscode.FileSystemProvider { throw Error("Unable to get key credential."); } - const serviceClient = new DataLakeServiceClient(dfsEndpoint, credential); + const serviceClient = new DataLakeServiceClient(dfsEndpoint, credential, withProxyOptions(dfsEndpoint)); const fileSystemClient = serviceClient.getFileSystemClient(containerName); const pathUrl = new URL(dfsEndpoint); pathUrl.pathname = `${containerName}/${blobPath}`; - const pathClient = new DataLakePathClient(pathUrl.toString(), credential); + const pathClient = new DataLakePathClient(pathUrl.toString(), credential, withProxyOptions(dfsEndpoint)); return { fileSystemClient, pathClient }; }