# Norton 360 SSL Interception — Node.js / npm / Python / uv / MCP Startup Failures

This guide addresses SSL/TLS certificate verification failures caused by Norton 360's
Web/Mail Shield intercepting HTTPS traffic on Windows. This affects:

- **Node.js-based MCP servers** launched via `npx` (e.g. Playwright MCP)
- **Python-based MCP servers** launched via `uv` (e.g. SharePoint MCP, MS Teams MCP)
- General `npm install`, `npm exec`, `uv run`, and `pip install` operations

---

## Symptom

MCP servers configured with `npx` fail to start in Cursor with a timeout error:

```
MCP error -32001: Request timed out
```

Cursor logs show a connection attempt that hangs for 60+ seconds before failing:

```
[info]  [V2 FSM] connection:connect_start: conn=idle,auth=unknown -> conn=connecting,auth=unknown
[warning] Connection failed: MCP error -32001: Request timed out
[warning] [V2 FSM] connection:connect_failure: conn=connecting,auth=unknown -> conn=failed,auth=unknown
```

npm debug logs (`npm-cache\_logs\`) reveal the underlying SSL failure:

```
http fetch GET https://registry.npmjs.org/@playwright%2fmcp attempt 1 failed with UNABLE_TO_VERIFY_LEAF_SIGNATURE
http fetch GET https://registry.npmjs.org/@playwright%2fmcp attempt 2 failed with UNABLE_TO_VERIFY_LEAF_SIGNATURE
http fetch GET https://registry.npmjs.org/@playwright%2fmcp attempt 3 failed with UNABLE_TO_VERIFY_LEAF_SIGNATURE
http fetch GET 200 https://registry.npmjs.org/@playwright%2fmcp 70318ms (cache stale)
```

The first 3 SSL verification attempts fail. The 4th eventually succeeds after ~70 seconds
(using a stale cache fallback), but this exceeds Cursor's ~60-second MCP startup timeout.

Running the same `npx` command directly in PowerShell may work, but with a noticeable
delay on first invocation after cache expiry.

---

## Symptom: Python / uv MCP Servers

MCP servers configured with `uv` fail to start in Cursor with a connection closed error:

```
MCP error -32000: Connection closed
```

Cursor logs show the SSL failure immediately (no retry delay like npm):

```
[error]   × Failed to fetch: `https://pypi.org/simple/fastmcp/`
  ├─▶ Request failed after 3 retries
  ├─▶ error sending request for url (https://pypi.org/simple/fastmcp/)
  ├─▶ client error (Connect)
  ╰─▶ invalid peer certificate: UnknownIssuer
  help: Consider enabling use of system TLS certificates with the `--native-tls` command-line flag
[warning] Connection failed: MCP error -32000: Connection closed
```

This occurs when `uv` needs to download dependencies from PyPI for the first time (new
MCP server, deleted `.venv`, or dependency version bump). Existing MCP servers with a
populated `.venv` are unaffected until their next dependency resolution.

---

## Root Cause

Norton 360's **Web/Mail Shield** performs HTTPS/SSL inspection (man-in-the-middle) on
outbound connections. It intercepts HTTPS requests, terminates the TLS session, inspects
the traffic, and re-encrypts it using Norton's own root CA certificate.

Node.js maintains its own CA certificate bundle (compiled into the binary) and does **not**
trust the Windows certificate store by default. When npm connects to `registry.npmjs.org`,
it receives Norton's certificate instead of the real one and rejects it with
`UNABLE_TO_VERIFY_LEAF_SIGNATURE`.

Similarly, `uv` (the fast Python package installer) uses its own bundled certificate store
by default. When it connects to `pypi.org`, it receives Norton's certificate and rejects
it with `invalid peer certificate: UnknownIssuer`.

### Why It Works on macOS

macOS machines may not have Norton installed, or Norton's macOS version may handle SSL
inspection differently. Additionally, some Node.js distributions on macOS are configured
to use the system keychain.

### Why It Worked Before

Norton may have recently updated its Web/Mail Shield, or a Node.js/npm update changed
SSL verification strictness. Windows updates can also affect certificate handling behavior.

---

## Diagnosis

### Step 1: Confirm Norton Is Intercepting HTTPS

Run in PowerShell:

```powershell
$req = [System.Net.HttpWebRequest]::Create("https://registry.npmjs.org")
$req.GetResponse() | Out-Null
$cert = $req.ServicePoint.Certificate
Write-Output "Issuer: $($cert.Issuer)"
Write-Output "Subject: $($cert.Subject)"
```

If Norton is intercepting, the **Issuer** will show Norton's CA:

```
Issuer: CN=Norton Web/Mail Shield Root, O=Norton Web/Mail Shield, OU=generated by Norton Antivirus for SSL/TLS scanning
Subject: CN=npmjs.org
```

A non-intercepted connection would show the real CA (e.g. Cloudflare, DigiCert).

### Step 2: Check npm Debug Logs

npm writes verbose logs to `%LOCALAPPDATA%\npm-cache\_logs\`. Look for the most recent
log file and search for `UNABLE_TO_VERIFY_LEAF_SIGNATURE` or `SELF_SIGNED_CERT_IN_CHAIN`.

---

## Fix: Export Norton's Root CA and Configure Node.js

This is the proper fix. It tells Node.js to trust Norton's intercepting CA certificate
without disabling SSL verification.

### Step 1: Create a Folder for the Certificate

```powershell
mkdir "$env:USERPROFILE\.ssl"
```

### Step 2: Export Norton's Root CA from the Windows Certificate Store

```powershell
$nortonCert = Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "*Norton*" } | Select-Object -First 1
$pemPath = "$env:USERPROFILE\.ssl\norton-ca.pem"
$pem = "-----BEGIN CERTIFICATE-----`n" + [Convert]::ToBase64String($nortonCert.RawData, 'InsertLineBreaks') + "`n-----END CERTIFICATE-----"
$pem | Out-File -Encoding ASCII $pemPath
Write-Output "Exported to: $pemPath"
```

### Step 3: Set the NODE_EXTRA_CA_CERTS Environment Variable

```powershell
[System.Environment]::SetEnvironmentVariable("NODE_EXTRA_CA_CERTS", "$env:USERPROFILE\.ssl\norton-ca.pem", "User")
```

This sets a **permanent** user-level environment variable. All future Node.js processes
(including those spawned by Cursor) will trust Norton's CA.

### Step 4: Verify

```powershell
# Verify the certificate file exists
Get-Content "$env:USERPROFILE\.ssl\norton-ca.pem"

# Verify the environment variable is set (open a NEW PowerShell window first)
echo $env:NODE_EXTRA_CA_CERTS
```

### Step 5: Restart Cursor

Close and reopen Cursor. The MCP server should now start without timeout errors.

---

## Fix: Configure uv to Use System TLS (Python MCP Servers)

For Python-based MCP servers launched via `uv`, add the `UV_NATIVE_TLS` environment
variable to the MCP server's configuration in `mcp.json`. This tells `uv` to use the
Windows system certificate store instead of its bundled certificate bundle.

### Per-Server Fix (mcp.json)

Add `"env": { "UV_NATIVE_TLS": "1" }` to each `uv`-based MCP server entry:

```json
"sharepoint": {
  "command": "uv",
  "args": [
    "--directory",
    "C:\\<path-to-repo>\\mcp-perf-suite\\sharepoint-mcp",
    "run",
    "sharepoint.py"
  ],
  "env": {
    "UV_NATIVE_TLS": "1"
  }
}
```

### System-Wide Fix (Environment Variable)

Alternatively, set `UV_NATIVE_TLS` as a permanent user-level environment variable so
all `uv` invocations use system TLS:

```powershell
[System.Environment]::SetEnvironmentVariable("UV_NATIVE_TLS", "1", "User")
```

Restart Cursor after applying either fix.

### Recommendation

Add `UV_NATIVE_TLS` to **all** `uv`-based MCP servers proactively, even those that
currently work. Existing servers with a populated `.venv` won't hit this issue until
their next dependency resolution (e.g. after deleting `.venv`, updating a dependency,
or setting up on a new machine).

---

## Alternative Workarounds

### Workaround A: Disable Norton SSL Scanning

Open Norton 360 > **Settings** > **Firewall** > **General Settings** > disable
**HTTPS Scanning** or **SSL Connection Monitoring**. This stops Norton from intercepting
HTTPS traffic entirely.

**Trade-off:** Reduces Norton's web protection coverage.

### Workaround B: Disable npm Strict SSL (Not Recommended)

```powershell
npm config set strict-ssl false
```

**Trade-off:** Disables SSL certificate verification for all npm operations. Use only as
a temporary measure.

### Workaround C: Global Install to Bypass npx Registry Lookup

Install the MCP package globally so Cursor doesn't need `npx` to resolve `@latest` on
every startup:

```powershell
npm config set strict-ssl false
npm install -g @playwright/mcp
npm config set strict-ssl true
```

Then update `mcp.json` to use the global binary directly:

```json
{
  "command": "mcp-server-playwright",
  "args": ["--config", "C:\\path\\to\\.playwright-mcp\\config.json"],
  "type": "stdio"
}
```

**Trade-off:** Loses automatic updates from `@latest`. Run
`npm install -g @playwright/mcp` periodically to update.

---

## Scope of Impact

This issue affects **any** tool that makes HTTPS connections using its own certificate
bundle on a machine with Norton 360 SSL inspection enabled:

### Node.js / npm

- `npx` commands (all MCP servers using `npx @package/name@latest`)
- `npm install` / `npm exec`
- Node.js `https.request()` and `fetch()` calls
- Any MCP server that makes outbound HTTPS calls during initialization

The `NODE_EXTRA_CA_CERTS` fix resolves it for all Node.js-based tools.

### Python / uv / pip

- `uv run` / `uv sync` / `uv pip install` (all MCP servers using `uv`)
- `pip install` (if using pip directly)
- Python `requests` / `httpx` / `urllib3` calls

The `UV_NATIVE_TLS=1` fix resolves it for all `uv`-based tools. For `pip` and Python
HTTP libraries, set `SSL_CERT_FILE` or `REQUESTS_CA_BUNDLE` to the exported Norton CA
PEM file.

---

## Related References

- [microsoft/playwright-mcp#1082](https://github.com/microsoft/playwright-mcp/issues/1082) — Playwright MCP startup timeout on Windows
- [microsoft/playwright-mcp#911](https://github.com/microsoft/playwright-mcp/issues/911) — Long-running operations causing client timeouts
- [openai/codex#3994](https://github.com/openai/codex/pull/3994) — Fix adding Windows environment variables for MCP startup
- [Node.js docs: NODE_EXTRA_CA_CERTS](https://nodejs.org/api/cli.html#node_extra_ca_certsfile)
- [uv docs: UV_NATIVE_TLS](https://docs.astral.sh/uv/configuration/environment/#uv_native_tls)

---

*Last updated: May 12, 2026*
