diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f6fe730ec37..9f5a3b39e71a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,10 @@ implementation (#4414).
 - The last opened folder/workspace is no longer stored separately in the
   settings file (we rely on the already-existing query object instead).
 
+### Added
+
+- `VSCODE_PROXY_URI` env var for use in the terminal and extensions.
+
 ### Deprecated
 
 - `--link` is now deprecated (#4562).
diff --git a/ci/dev/postinstall.sh b/ci/dev/postinstall.sh
index f8b8974bfcac..78f26cc631bd 100755
--- a/ci/dev/postinstall.sh
+++ b/ci/dev/postinstall.sh
@@ -3,20 +3,30 @@ set -euo pipefail
 
 main() {
   cd "$(dirname "$0")/../.."
+  source ./ci/lib.sh
 
-  echo "Installing code-server test dependencies..."
+  pushd test
+  echo "Installing dependencies for $PWD"
+  yarn install
+  popd
 
   local args=(install)
   if [[ ${CI-} ]]; then
     args+=(--frozen-lockfile)
   fi
 
-  cd test
+  pushd test
+  echo "Installing dependencies for $PWD"
   yarn "${args[@]}"
-  cd ..
+  popd
 
-  cd vendor
-  echo "Installing vendor dependencies..."
+  pushd test/e2e/extensions/test-extension
+  echo "Installing dependencies for $PWD"
+  yarn "${args[@]}"
+  popd
+
+  pushd vendor
+  echo "Installing dependencies for $PWD"
 
   # We install in 'modules' instead of 'node_modules' because VS Code's
   # extensions use a webpack config which cannot differentiate between its own
@@ -33,6 +43,8 @@ main() {
 
   # Finally, run the vendor `postinstall`
   yarn run postinstall
+
+  popd
 }
 
 main "$@"
diff --git a/ci/dev/test-e2e.sh b/ci/dev/test-e2e.sh
index bca78c5558b6..cf3e53d118e9 100755
--- a/ci/dev/test-e2e.sh
+++ b/ci/dev/test-e2e.sh
@@ -13,6 +13,11 @@ main() {
 
   source ./ci/lib.sh
 
+  pushd test/e2e/extensions/test-extension
+  echo "Building test extension"
+  yarn build
+  popd
+
   local dir="$PWD"
   if [[ ! ${CODE_SERVER_TEST_ENTRY-} ]]; then
     echo "Set CODE_SERVER_TEST_ENTRY to test another build of code-server"
diff --git a/ci/dev/test-unit.sh b/ci/dev/test-unit.sh
index f82413b93288..3578d87e647d 100755
--- a/ci/dev/test-unit.sh
+++ b/ci/dev/test-unit.sh
@@ -6,6 +6,7 @@ main() {
 
   source ./ci/lib.sh
 
+  echo "Building test plugin"
   pushd test/unit/node/test-plugin
   make -s out/index.js
   popd
diff --git a/test/e2e/extensions.test.ts b/test/e2e/extensions.test.ts
new file mode 100644
index 000000000000..f458ac29d4f9
--- /dev/null
+++ b/test/e2e/extensions.test.ts
@@ -0,0 +1,12 @@
+import { describe, test } from "./baseFixture"
+
+describe("Extensions", true, () => {
+  // This will only work if the test extension is loaded into code-server.
+  test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => {
+    const address = await codeServerPage.address()
+
+    await codeServerPage.executeCommandViaMenus("code-server: Get proxy URI")
+
+    await codeServerPage.page.waitForSelector(`text=${address}/proxy/{{port}}`)
+  })
+})
diff --git a/test/e2e/extensions/test-extension/.gitignore b/test/e2e/extensions/test-extension/.gitignore
new file mode 100644
index 000000000000..e7b307d8c4f7
--- /dev/null
+++ b/test/e2e/extensions/test-extension/.gitignore
@@ -0,0 +1 @@
+/extension.js
diff --git a/test/e2e/extensions/test-extension/extension.ts b/test/e2e/extensions/test-extension/extension.ts
new file mode 100644
index 000000000000..dcbd6dde7bc0
--- /dev/null
+++ b/test/e2e/extensions/test-extension/extension.ts
@@ -0,0 +1,13 @@
+import * as vscode from "vscode"
+
+export function activate(context: vscode.ExtensionContext) {
+  context.subscriptions.push(
+    vscode.commands.registerCommand("codeServerTest.proxyUri", () => {
+      if (process.env.VSCODE_PROXY_URI) {
+        vscode.window.showInformationMessage(process.env.VSCODE_PROXY_URI)
+      } else {
+        vscode.window.showErrorMessage("No proxy URI was set")
+      }
+    }),
+  )
+}
diff --git a/test/e2e/extensions/test-extension/package.json b/test/e2e/extensions/test-extension/package.json
new file mode 100644
index 000000000000..82be6fe52ced
--- /dev/null
+++ b/test/e2e/extensions/test-extension/package.json
@@ -0,0 +1,29 @@
+{
+  "name": "code-server-extension",
+  "description": "code-server test extension",
+  "version": "0.0.1",
+  "publisher": "cdr",
+  "activationEvents": [
+    "onCommand:codeServerTest.proxyUri"
+  ],
+  "engines": {
+    "vscode": "^1.56.0"
+  },
+  "main": "./extension.js",
+  "contributes": {
+    "commands": [
+      {
+        "command": "codeServerTest.proxyUri",
+        "title": "Get proxy URI",
+        "category": "code-server"
+      }
+    ]
+  },
+  "devDependencies": {
+    "@types/vscode": "^1.56.0",
+    "typescript": "^4.0.5"
+  },
+  "scripts": {
+    "build": "tsc extension.ts"
+  }
+}
diff --git a/test/e2e/extensions/test-extension/tsconfig.json b/test/e2e/extensions/test-extension/tsconfig.json
new file mode 100644
index 000000000000..9840655c5d4b
--- /dev/null
+++ b/test/e2e/extensions/test-extension/tsconfig.json
@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "target": "es2020",
+    "module": "commonjs",
+    "outDir": ".",
+    "strict": true,
+    "baseUrl": "./"
+  },
+  "include": ["./extension.ts"]
+}
diff --git a/test/e2e/extensions/test-extension/yarn.lock b/test/e2e/extensions/test-extension/yarn.lock
new file mode 100644
index 000000000000..363247117ecf
--- /dev/null
+++ b/test/e2e/extensions/test-extension/yarn.lock
@@ -0,0 +1,13 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/vscode@^1.56.0":
+  version "1.57.0"
+  resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.57.0.tgz#cc648e0573b92f725cd1baf2621f8da9f8bc689f"
+  integrity sha512-FeznBFtIDCWRluojTsi9c3LLcCHOXP5etQfBK42+ixo1CoEAchkw39tuui9zomjZuKfUVL33KZUDIwHZ/xvOkQ==
+
+typescript@^4.0.5:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
+  integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
diff --git a/test/e2e/models/CodeServer.ts b/test/e2e/models/CodeServer.ts
index 12d6ef46180a..7ae8f5b76b00 100644
--- a/test/e2e/models/CodeServer.ts
+++ b/test/e2e/models/CodeServer.ts
@@ -88,6 +88,8 @@ export class CodeServer {
           path.join(dir, "config.yaml"),
           "--user-data-dir",
           dir,
+          "--extensions-dir",
+          path.join(__dirname, "../extensions"),
           // The last argument is the workspace to open.
           dir,
         ],
diff --git a/test/e2e/terminal.test.ts b/test/e2e/terminal.test.ts
index 5012fdef5a9b..b5b2d90c9b48 100644
--- a/test/e2e/terminal.test.ts
+++ b/test/e2e/terminal.test.ts
@@ -5,19 +5,14 @@ import { clean, tmpdir } from "../utils/helpers"
 import { describe, expect, test } from "./baseFixture"
 
 describe("Integrated Terminal", true, () => {
-  // Create a new context with the saved storage state
-  // so we don't have to logged in
-  const testFileName = "pipe"
-  const testString = "new string test from e2e test"
-
   const testName = "integrated-terminal"
   test.beforeAll(async () => {
     await clean(testName)
   })
 
-  test("should echo a string to a file", async ({ codeServerPage }) => {
+  test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => {
     const tmpFolderPath = await tmpdir(testName)
-    const tmpFile = path.join(tmpFolderPath, testFileName)
+    const tmpFile = path.join(tmpFolderPath, "pipe")
 
     const command = `mkfifo '${tmpFile}' && cat '${tmpFile}'`
     const exec = util.promisify(cp.exec)
@@ -27,12 +22,12 @@ describe("Integrated Terminal", true, () => {
     await codeServerPage.focusTerminal()
 
     await codeServerPage.page.waitForLoadState("load")
-    await codeServerPage.page.keyboard.type(`echo ${testString} > ${tmpFile}`)
+    await codeServerPage.page.keyboard.type(`printenv VSCODE_PROXY_URI > ${tmpFile}`)
     await codeServerPage.page.keyboard.press("Enter")
     // It may take a second to process
     await codeServerPage.page.waitForTimeout(1000)
 
     const { stdout } = await output
-    expect(stdout).toMatch(testString)
+    expect(stdout).toMatch(await codeServerPage.address())
   })
 })
diff --git a/test/playwright.config.ts b/test/playwright.config.ts
index c969dabfad4a..599914777f1e 100644
--- a/test/playwright.config.ts
+++ b/test/playwright.config.ts
@@ -25,12 +25,10 @@ const config: PlaywrightTestConfig = {
       name: "Chromium",
       use: { browserName: "chromium" },
     },
-
     {
       name: "Firefox",
       use: { browserName: "firefox" },
     },
-
     {
       name: "WebKit",
       use: { browserName: "webkit" },
diff --git a/vendor/package.json b/vendor/package.json
index 8ac4d1bc85c9..671d67d34ca0 100644
--- a/vendor/package.json
+++ b/vendor/package.json
@@ -7,6 +7,6 @@
     "postinstall": "./postinstall.sh"
   },
   "devDependencies": {
-    "code-oss-dev": "cdr/vscode#69a6ce45fc5b883aa8a950e10b79fd083eb0a7d7"
+    "code-oss-dev": "cdr/vscode#d4c3c65d5e17a240a95e735a349e311aaf721b60"
   }
 }
diff --git a/vendor/yarn.lock b/vendor/yarn.lock
index d826b0c97053..4b11e6cdc8ef 100644
--- a/vendor/yarn.lock
+++ b/vendor/yarn.lock
@@ -274,9 +274,9 @@ clone-response@^1.0.2:
   dependencies:
     mimic-response "^1.0.0"
 
-code-oss-dev@cdr/vscode#69a6ce45fc5b883aa8a950e10b79fd083eb0a7d7:
+code-oss-dev@cdr/vscode#d4c3c65d5e17a240a95e735a349e311aaf721b60:
   version "1.63.0"
-  resolved "https://codeload.github.com/cdr/vscode/tar.gz/69a6ce45fc5b883aa8a950e10b79fd083eb0a7d7"
+  resolved "https://codeload.github.com/cdr/vscode/tar.gz/d4c3c65d5e17a240a95e735a349e311aaf721b60"
   dependencies:
     "@microsoft/applicationinsights-web" "^2.6.4"
     "@parcel/watcher" "2.0.3"