microsoft / microsoft/vscode-azurefunctions

[Bug] macOS Debugger Fails with UNABLE_TO_VERIFY_LEAF_SIGNATURE on HTTPS

Open
#4,635 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
319
Forks
158
Avg merge
2d 13h
Merged PRs (30d)
4

Description

Description

When attempting to debug a .NET Isolated Azure Function on macOS using HTTPS, the debugger fails to attach. The preLaunchTask successfully starts the function host, but the extension's internal health check to the /admin/host/status endpoint times out after 60 seconds, throwing a RestError: UNABLE_TO_VERIFY_LEAF_SIGNATURE.

This occurs even when all local configurations are correct, pointing to a bug in how the extension's runtime environment handles TLS connections on macOS.

Key Findings

A detailed investigation revealed the following:

  1. HTTP Works: Switching the debug task to use HTTP works perfectly, and the debugger attaches instantly.
  2. curl Works: While the extension is failing, a manual curl https://localhost:<port>/admin/host/status command from the terminal succeeds and returns a "Running" state.
  3. Certificate is Trusted: The development certificate has been explicitly trusted via dotnet dev-certs https --trust and is verified as trusted in the macOS Keychain.
  4. No Environmental Blocks: The issue persists with the macOS firewall turned off and no other security software or proxies configured.
Root Cause Analysis

An analysis of the extension's source code shows that the startFuncTask function correctly attempts to disable certificate validation for the localhost health check. It explicitly sets the { rejectUnauthorized: false } option before calling sendRequestWithTimeout.

Despite this correct implementation, the underlying HTTP request still fails with a certificate validation error. This proves that the rejectUnauthorized: false flag is not being honored by the underlying execution stack (either the specific version of Node.js used by VS Code or the @azure/core-rest-pipeline library) on macOS.

Like because rejectUnauthorized is honored by underlying library when it's value is true.

export async function sendRequestWithTimeout(context: IActionContext, options: types.AzExtRequestPrepareOptions, timeout: number, clientInfo: types.AzExtGenericClientInfo): Promise<types.AzExtPipelineResponse> {
    const request: PipelineRequest = createPipelineRequest({
        ...options,
        timeout
    });

    if (options.rejectUnauthorized) {
        request.agent = new HttpsAgent({ rejectUnauthorized: options.rejectUnauthorized });
    }

    const client = await createGenericClient(context, clientInfo, { noRetryPolicy: true, addStatusCodePolicy: true });
    return await client.sendRequest(request);
}
Environment

OS Version: MacOS 15.6 (24G84)
VS Code Version: 1.102.1 (Universal)
Extension Version: 1.18.0 (current)
.NET SDK Version: 9.0.303
Core Tools Version: 4.1.0+7ff2567e43c1ae7471cea7394452c1447661e175 (64-bit)
Function Runtime Version: 4.1040.300.25317

Location:

startFuncTask (/Users/***/Code/vscode-azurefunctions/src/commands/pickFuncProcess.ts:158)

Error:

{
  name: "RestError",
  code: "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
  statusCode: undefined,
  request: {
      url: "https://localhost:<port>/admin/host/status",
      body: undefined,
      headers: {
        _headersMap: {
        },
      },
      method: "GET",
      timeout: 500,
      multipartBody: undefined,
      formData: undefined,
      disableKeepAlive: false,
      proxySettings: undefined,
      streamResponseStatusCodes: undefined,
      withCredentials: false,
      abortSignal: undefined,
      tracingOptions: undefined,
      onUploadProgress: undefined,
      onDownloadProgress: undefined,
      requestId: "d86cf8cc-ebe4-460a-a9f4-33d711eab2ec",
      allowInsecureConnection: true,
      enableBrowserStreams: false,
      agent: undefined,
      tlsSettings: undefined,
    }
}

Error Stack Trace:

RestError: unable to verify the first certificate
    at ClientRequest.<anonymous> (/Users/***/Code/vscode-azurefunctions/node_modules/@azure/core-rest-pipeline/src/nodeHttpClient.ts:241:11)
    at Object.onceWrapper (node:events:633:26)
    at ClientRequest.emit (node:events:518:28)
    at emitErrorEvent (node:_http_client:104:11)
    at TLSSocket.socketErrorListener (node:_http_client:518:5)
    at TLSSocket.emit (node:events:518:28)
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at processTicksAndRejections (node:internal/process/task_queues:90:21)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at src/commands/pickFuncProcess.ts, especially startFuncTask around line 158, and inspect sendRequestWithTimeout together with the @azure/core-rest-pipeline nodeHttpClient stack shown in the report. Reproduce the HTTPS health check on macOS and trace why the request has no effective agent despite rejectUnauthorized being disabled. Done means the debugger attaches successfully over HTTPS while HTTP behavior remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, node.js, typescript
Domain
developer-experience, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.