mirror of
https://github.com/coder/code-server.git
synced 2026-11-02 22:09:11 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
279b5702aa | ||
|
|
27baccf843 | ||
|
|
8aab1b0d72 | ||
|
|
777c493a9b | ||
|
|
f07405cba3 | ||
|
|
c5fee2d2f9 | ||
|
|
83670e50c6 | ||
|
|
41cf6f7eaf | ||
|
|
78aad47ce6 |
@@ -1 +1 @@
|
|||||||
24.21.0
|
24.22.1
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ Code v99.99.999
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update bundled Node.js to 24.22.1 to fix a remote code execution bug in the
|
||||||
|
Brotli decoder (CVE-2026-111497).
|
||||||
|
- Reject compressed request bodies (`Content-Encoding: br`, `gzip`, or
|
||||||
|
`deflate`) instead of decompressing them.
|
||||||
|
|
||||||
|
## [4.141.0](https://github.com/coder/code-server/releases/tag/v4.141.0) - 2026-10-08
|
||||||
|
|
||||||
Code v1.141.0
|
Code v1.141.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 3.55.0
|
version: 3.56.0
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
appVersion: 4.140.0
|
appVersion: 4.141.0
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ replicaCount: 1
|
|||||||
|
|
||||||
image:
|
image:
|
||||||
repository: codercom/code-server
|
repository: codercom/code-server
|
||||||
tag: '4.140.0'
|
tag: '4.141.0'
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
# Specifies one or more secrets to be used when pulling images from a
|
# Specifies one or more secrets to be used when pulling images from a
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1055,9 +1055,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||||
"version": "5.0.9",
|
"version": "5.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
|
||||||
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
|
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1779,9 +1779,9 @@
|
|||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.18",
|
"version": "1.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
|
||||||
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
|
"integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
33
patches/node-version.diff
Normal file
33
patches/node-version.diff
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
Bump the bundled Node.js version
|
||||||
|
|
||||||
|
Pull in Node.js security fixes (Brotli decoder RCE) ahead of upstream. Drop
|
||||||
|
this patch once upstream targets this version or newer.
|
||||||
|
|
||||||
|
Index: code-server/lib/vscode/remote/.npmrc
|
||||||
|
===================================================================
|
||||||
|
--- code-server.orig/lib/vscode/remote/.npmrc
|
||||||
|
+++ code-server/lib/vscode/remote/.npmrc
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
disturl="https://nodejs.org/dist"
|
||||||
|
-target="24.21.0"
|
||||||
|
+target="24.22.1"
|
||||||
|
ms_build_id="474716"
|
||||||
|
runtime="node"
|
||||||
|
build_from_source="true"
|
||||||
|
Index: code-server/lib/vscode/build/checksums/nodejs.txt
|
||||||
|
===================================================================
|
||||||
|
--- code-server.orig/lib/vscode/build/checksums/nodejs.txt
|
||||||
|
+++ code-server/lib/vscode/build/checksums/nodejs.txt
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
-bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057 node-v24.21.0-darwin-arm64.tar.gz
|
||||||
|
-1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097 node-v24.21.0-darwin-x64.tar.gz
|
||||||
|
-724282c3b43aec998aa9527380465b45d229e021b58035f5f4f63095eabfe5d5 node-v24.21.0-linux-arm64.tar.gz
|
||||||
|
-6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff node-v24.21.0-linux-x64.tar.gz
|
||||||
|
-dff59da18b6ffe1bf1ca99e1d2af4906080c481740619f5b5098c0fca28bd9b7 win-arm64/node.exe
|
||||||
|
-ba4e6d110e8c1592a1ecd390f6b05f3da124b13871a5be62b341a07a853c6c32 win-x64/node.exe
|
||||||
|
+89c682593d76cd44233fa53b1563264e7de9cbb189cad169135c5b4fadfbc0a7 node-v24.22.1-darwin-arm64.tar.gz
|
||||||
|
+d132557e7ee2b85af08b0c8c34338e65c6430f2238d01f727728ef42dc83b035 node-v24.22.1-darwin-x64.tar.gz
|
||||||
|
+33c37be234ba8bee041961047b19160f4d7d0f164e8e246f78bdd10feff148b3 node-v24.22.1-linux-arm64.tar.gz
|
||||||
|
+eb6728e77c576a35162c122c45ccadf224d89ff00abc8dae46cc2897c6002d7c node-v24.22.1-linux-x64.tar.gz
|
||||||
|
+8bea91dc437fe01e7d92ef0efc6c9eaf654369aaba9b2d3e9841552ea7b95a00 win-arm64/node.exe
|
||||||
|
+d702daa815eabbff763c7d3808e235cf031cf2ca70211b56847bc72f51e240e5 win-x64/node.exe
|
||||||
@@ -27,3 +27,4 @@ app-name.diff
|
|||||||
remote-storage.diff
|
remote-storage.diff
|
||||||
remote-secret-storage.diff
|
remote-secret-storage.diff
|
||||||
csp-hashes.diff
|
csp-hashes.diff
|
||||||
|
node-version.diff
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as crypto from "crypto"
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import { load } from "js-yaml"
|
import { load } from "js-yaml"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
|
import { expandTilde, generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
|
||||||
import { EditorSessionManagerClient } from "./vscodeSocket"
|
import { EditorSessionManagerClient } from "./vscodeSocket"
|
||||||
|
|
||||||
export enum Feature {
|
export enum Feature {
|
||||||
@@ -436,7 +436,11 @@ export const parse = (
|
|||||||
|
|
||||||
const option = options[key]
|
const option = options[key]
|
||||||
if (option.type === "boolean") {
|
if (option.type === "boolean") {
|
||||||
;(args[key] as boolean) = true
|
// Booleans set to false (for example `--flag=false`, which is also how
|
||||||
|
// false values from the config file are passed in) are ignored.
|
||||||
|
if (value !== "false") {
|
||||||
|
;(args[key] as boolean) = true
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +466,7 @@ export const parse = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (option.path) {
|
if (option.path) {
|
||||||
value = path.resolve(value)
|
value = path.resolve(expandTilde(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import i18next, { init } from "i18next"
|
|||||||
import * as en from "./locales/en.json"
|
import * as en from "./locales/en.json"
|
||||||
import * as ja from "./locales/ja.json"
|
import * as ja from "./locales/ja.json"
|
||||||
import * as th from "./locales/th.json"
|
import * as th from "./locales/th.json"
|
||||||
|
import * as tr from "./locales/tr.json"
|
||||||
import * as ur from "./locales/ur.json"
|
import * as ur from "./locales/ur.json"
|
||||||
import * as zhCn from "./locales/zh-cn.json"
|
import * as zhCn from "./locales/zh-cn.json"
|
||||||
|
|
||||||
@@ -16,6 +17,9 @@ const defaultResources = {
|
|||||||
th: {
|
th: {
|
||||||
translation: th,
|
translation: th,
|
||||||
},
|
},
|
||||||
|
tr: {
|
||||||
|
translation: tr,
|
||||||
|
},
|
||||||
ja: {
|
ja: {
|
||||||
translation: ja,
|
translation: ja,
|
||||||
},
|
},
|
||||||
|
|||||||
13
src/node/i18n/locales/tr.json
Normal file
13
src/node/i18n/locales/tr.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"LOGIN_TITLE": "{{app}} girişi",
|
||||||
|
"LOGIN_BELOW": "Lütfen aşağıdan giriş yapın.",
|
||||||
|
"WELCOME": "{{app}} uygulamasına hoş geldiniz",
|
||||||
|
"LOGIN_PASSWORD": "Parola için yapılandırma dosyasını kontrol edin.",
|
||||||
|
"LOGIN_USING_ENV_PASSWORD": "Parola $PASSWORD ortam değişkeninden ayarlandı.",
|
||||||
|
"LOGIN_USING_HASHED_PASSWORD": "Parola $HASHED_PASSWORD ortam değişkeninden ayarlandı.",
|
||||||
|
"SUBMIT": "GÖNDER",
|
||||||
|
"PASSWORD_PLACEHOLDER": "PAROLA",
|
||||||
|
"LOGIN_RATE_LIMIT": "Giriş denemesi sınırına ulaşıldı!",
|
||||||
|
"MISS_PASSWORD": "Parola eksik",
|
||||||
|
"INCORRECT_PASSWORD": "Parola yanlış."
|
||||||
|
}
|
||||||
@@ -132,8 +132,10 @@ export const register = async (
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.router.use(express.json())
|
// Reject compressed request bodies so unauthenticated requests cannot reach
|
||||||
app.router.use(express.urlencoded({ extended: true }))
|
// the zlib/Brotli decoders. Browsers never compress request bodies.
|
||||||
|
app.router.use(express.json({ inflate: false }))
|
||||||
|
app.router.use(express.urlencoded({ extended: true, inflate: false }))
|
||||||
|
|
||||||
app.router.use(
|
app.router.use(
|
||||||
"/_static",
|
"/_static",
|
||||||
|
|||||||
@@ -511,3 +511,18 @@ export function splitOnFirstEquals(str: string): [string, string | undefined] {
|
|||||||
const split = str.split(/=(.+)?/, 2)
|
const split = str.split(/=(.+)?/, 2)
|
||||||
return [split[0], split[1]]
|
return [split[0], split[1]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand a leading `~` to the home directory. The shell does this for
|
||||||
|
* command-line arguments but values from the config file are not expanded.
|
||||||
|
* `~user` forms are left alone.
|
||||||
|
*/
|
||||||
|
export function expandTilde(p: string): string {
|
||||||
|
if (p === "~") {
|
||||||
|
return os.homedir()
|
||||||
|
}
|
||||||
|
if (p.startsWith("~/") || p.startsWith(`~${path.sep}`)) {
|
||||||
|
return path.join(os.homedir(), p.slice(2))
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export async function makeEditorSessionManagerServer(
|
|||||||
): Promise<http.Server> {
|
): Promise<http.Server> {
|
||||||
const router = express()
|
const router = express()
|
||||||
|
|
||||||
router.use(express.json())
|
router.use(express.json({ inflate: false }))
|
||||||
|
|
||||||
router.get<{}, GetSessionResponse | string | unknown, undefined, { filePath?: string }>(
|
router.get<{}, GetSessionResponse | string | unknown, undefined, { filePath?: string }>(
|
||||||
"/session",
|
"/session",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Level, logger } from "@coder/logger"
|
import { Level, logger } from "@coder/logger"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import {
|
import {
|
||||||
UserProvidedArgs,
|
UserProvidedArgs,
|
||||||
@@ -575,9 +576,33 @@ describe("parser", () => {
|
|||||||
"install-extension": ["extension.number1", "extension.number2"],
|
"install-extension": ["extension.number1", "extension.number2"],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
it("should expand ~ in path options from the config file", () => {
|
||||||
|
const configContents = `
|
||||||
|
user-data-dir: ~/.config/code-server-data
|
||||||
|
extensions-dir: ~/extensions
|
||||||
|
`
|
||||||
|
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
|
||||||
|
config: "/fake-config-path",
|
||||||
|
"user-data-dir": path.join(os.homedir(), ".config/code-server-data"),
|
||||||
|
"extensions-dir": path.join(os.homedir(), "extensions"),
|
||||||
|
})
|
||||||
|
})
|
||||||
it("should ignore optional strings set to false", async () => {
|
it("should ignore optional strings set to false", async () => {
|
||||||
expect(parse(["--cert=false"])).toEqual({})
|
expect(parse(["--cert=false"])).toEqual({})
|
||||||
})
|
})
|
||||||
|
it("should ignore booleans set to false", async () => {
|
||||||
|
expect(parse(["--disable-telemetry=false"])).toEqual({})
|
||||||
|
})
|
||||||
|
it("should parse booleans set to false in the config file", () => {
|
||||||
|
const configContents = `
|
||||||
|
disable-telemetry: false
|
||||||
|
disable-update-check: true
|
||||||
|
`
|
||||||
|
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
|
||||||
|
config: "/fake-config-path",
|
||||||
|
"disable-update-check": true,
|
||||||
|
})
|
||||||
|
})
|
||||||
it("should use last flag", async () => {
|
it("should use last flag", async () => {
|
||||||
expect(parse(["--port", "8081", "--port", "8082"])).toEqual({
|
expect(parse(["--port", "8081", "--port", "8082"])).toEqual({
|
||||||
port: 8082,
|
port: 8082,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { loadCustomStrings } from "../../../src/node/i18n"
|
import i18n, { loadCustomStrings } from "../../../src/node/i18n"
|
||||||
|
|
||||||
describe("i18n", () => {
|
describe("i18n", () => {
|
||||||
|
it("includes Turkish translations", () => {
|
||||||
|
expect(i18n.getResource("tr", "translation", "WELCOME")).toBe("{{app}} uygulamasına hoş geldiniz")
|
||||||
|
})
|
||||||
|
|
||||||
let tempDir: string
|
let tempDir: string
|
||||||
let validJsonFile: string
|
let validJsonFile: string
|
||||||
let invalidJsonFile: string
|
let invalidJsonFile: string
|
||||||
|
|||||||
@@ -75,6 +75,17 @@ describe("login", () => {
|
|||||||
expect(htmlContent).toContain("Missing password")
|
expect(htmlContent).toContain("Missing password")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should reject compressed request bodies", async () => {
|
||||||
|
for (const encoding of ["br", "gzip", "deflate"]) {
|
||||||
|
const resp = await codeServer().fetch("/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded", "Content-Encoding": encoding },
|
||||||
|
body: "password=test",
|
||||||
|
})
|
||||||
|
expect(resp.status).toBe(415)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it("should return HTML with 'Incorrect password' message", async () => {
|
it("should return HTML with 'Incorrect password' message", async () => {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append("password", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
params.append("password", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as cp from "child_process"
|
import * as cp from "child_process"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { generateUuid } from "../../../src/common/util"
|
import { generateUuid } from "../../../src/common/util"
|
||||||
import { tmpdir } from "../../../src/node/constants"
|
import { tmpdir } from "../../../src/node/constants"
|
||||||
@@ -623,3 +624,18 @@ describe("splitOnFirstEquals", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("expandTilde", () => {
|
||||||
|
it("should expand a bare ~", () => {
|
||||||
|
expect(util.expandTilde("~")).toBe(os.homedir())
|
||||||
|
})
|
||||||
|
it("should expand a leading ~/", () => {
|
||||||
|
expect(util.expandTilde("~/.config/code-server")).toBe(path.join(os.homedir(), ".config/code-server"))
|
||||||
|
})
|
||||||
|
it("should leave other paths alone", () => {
|
||||||
|
expect(util.expandTilde("/tmp/~/data")).toBe("/tmp/~/data")
|
||||||
|
expect(util.expandTilde("relative/~")).toBe("relative/~")
|
||||||
|
expect(util.expandTilde("~user/data")).toBe("~user/data")
|
||||||
|
expect(util.expandTilde("")).toBe("")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user