serve-web: /web-extension-resource always returns 403, so no web extension can be installed from the Extensions panel
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Does this issue occur when all extensions are disabled?: Yes
- VS Code Version: 1.134.0-insider (`server-linux-x64-web`, commit `fc62850c022bbba2c93b0202dd24f42d1e0b3882`). Also reproduced on stable 1.133.0.
- OS Version: Linux x64 (server); browser-independent
Steps to Reproduce:
1. Download and unpack the current insider web server build:
```
curl -sL "$(curl -s https://update.code.visualstudio.com/api/update/server-linux-x64-web/insider/latest | python3 -c 'import json,sys;print(json.load(sys.stdin)["url"])')" | tar xz
```
2. Start it: `./bin/code-server-insiders serve-local --port 8080 --host 127.0.0.1 --without-connection-token --accept-server-license-terms`
3. Request any marketplace asset through the extension-resource proxy:
```
curl -i 'http://127.0.0.1:8080/web-extension-resource/google.vscode-unpkg.net/google/reflowlist/0.8.1/extension/package.json'
```
Expected: `200`, with the asset proxied from `https://google.vscode-unpkg.net/...`.
Actual: `403 Request Forbidden`, returned in ~1.5ms with no outbound request made. Fetching the same
URL directly from the same host returns `200` (3576 bytes), so this is not network, egress or
marketplace availability.
In a browser this surfaces as every web extension (one declaring `browser` in its manifest) failing
to install from the Extensions panel with `403 Request Forbidden`.
### Cause
`WebClientServer.handle` in `src/vs/server/node/webClientServer.ts` matches the route prefix, checks
the next character is a slash, and then does not step over it. From the shipped bundle
(`out/server-main.js`, identifiers are minifier output, `uC` is `"/web-extension-resource"`):
```js
o.startsWith(uC) && o.charCodeAt(uC.length) === 47
? this._handleWebExtensionResource(t, e, o.substring(uC.length))
: ...
```
So `_handleWebExtensionResource` receives
`/google.vscode-unpkg.net/google/reflowlist/0.8.1/extension/package.json`, with the leading slash
still attached. It then splits that into an authority and a path at the first slash:
```js
authority: i.substring(0, i.indexOf("/")),
path: i.substring(i.indexOf("/") + 1)
```
Because the leading slash survived, `indexOf("/")` is 0, the authority is the empty string, and the
whole thing lands in the path. `_getResourceURLTemplateAuthority` returns `undefined` for that empty
authority while returning `vscode-unpkg.net` for the template, the two do not match, and the next
line returns 403 before fetching anything.
Instrumenting the bundle confirms it:
```
parsed={"scheme":"https","authority":"","path":"/google.vscode-unpkg.net/google/reflowlist/0.8.1/extension/package.json"}
tmplAuth="vscode-unpkg.net" reqAuth=undefined
```
That also explains why no client-side change helps: the request authority is always empty, whatever
the client sends, so every asset fails identically.
### Fix
`o.substring(uC.length)` becomes `o.substring(uC.length + 1)`.
Verified against a clean 1.134.0-insider tree: patch that one call, restart the server, and
| Asset | Before | After |
|---|---|---|
| `google/reflowlist/0.8.1/extension/package.json` | 403 | 200, 3576 bytes |
| `google/reflowlist/0.8.1/extension/dist/web/extension.js` | 403 | 200, 4494 bytes |
| `yzhang/markdown-all-in-one/3.6.3/extension/package.json` | 403 | 200, 33389 bytes |
| `samuelcolvin/jinjahtml/0.20.0/extension/package.json` | 403 | 200, 17181 bytes |
all matching the byte counts a direct fetch returns. Installing a web extension from the Extensions
panel then completes and the extension runs.
The `_handleStatic` branch immediately above has the same shape and the same omission, but there the
remainder is resolved against a local directory, where a leading slash is absorbed by the path join,
so only the extension-resource branch is affected.
### Impact
No web extension can be installed from the Extensions panel in any self-hosted `serve-web` /
`serve-local` deployment. Installing over the CLI (`code --install-extension`) still works, because
that path does not use this endpoint, and the extension then loads over
`/vscode-remote-resource?path=...`, which is unaffected.
Contributor guide
Assessment
This issue has not been assessed yet.