Fix AppImage launches from Linux desktops (#2439)

This commit is contained in:
stonegray
2026-07-28 13:59:26 +02:00
committed by GitHub
parent 76e336a1be
commit fd7061a8b2
3 changed files with 136 additions and 7 deletions
@@ -0,0 +1,82 @@
import { describe, expect, it } from "vitest";
import { configureLinuxSandbox } from "./linux-sandbox";
interface SandboxMetadata {
mode: number;
uid: number;
}
function configureWithSandbox(
sandbox: SandboxMetadata | Error,
platform: NodeJS.Platform = "linux",
) {
const disabledSwitches: string[] = [];
const inspectionErrors: unknown[] = [];
configureLinuxSandbox({
platform,
resourcesPath: "/opt/Paseo/resources",
statSandbox: () => {
if (sandbox instanceof Error) {
throw sandbox;
}
return sandbox;
},
disableSandbox: () => disabledSwitches.push("no-sandbox"),
reportInspectionError: (error) => inspectionErrors.push(error),
});
return { disabledSwitches, inspectionErrors };
}
function createFileSystemError(code: string): NodeJS.ErrnoException {
const error: NodeJS.ErrnoException = new Error(code);
error.code = code;
return error;
}
describe("configureLinuxSandbox", () => {
it("disables the sandbox when an AppImage mount strips SUID", () => {
expect(configureWithSandbox({ uid: 1000, mode: 0o755 })).toEqual({
disabledSwitches: ["no-sandbox"],
inspectionErrors: [],
});
});
it("keeps the sandbox for a root-owned 4755 helper", () => {
expect(configureWithSandbox({ uid: 0, mode: 0o4755 })).toEqual({
disabledSwitches: [],
inspectionErrors: [],
});
});
it("disables the sandbox when a SUID helper is not root-owned", () => {
expect(configureWithSandbox({ uid: 1000, mode: 0o4755 })).toEqual({
disabledSwitches: ["no-sandbox"],
inspectionErrors: [],
});
});
it("disables the sandbox when the helper is missing", () => {
expect(configureWithSandbox(createFileSystemError("ENOENT"))).toEqual({
disabledSwitches: ["no-sandbox"],
inspectionErrors: [],
});
});
it("keeps the sandbox and reports unexpected inspection failures", () => {
const permissionError = createFileSystemError("EACCES");
expect(configureWithSandbox(permissionError)).toEqual({
disabledSwitches: [],
inspectionErrors: [permissionError],
});
});
it("does not inspect or configure the sandbox outside Linux", () => {
expect(configureWithSandbox(createFileSystemError("EACCES"), "darwin")).toEqual({
disabledSwitches: [],
inspectionErrors: [],
});
});
});
@@ -0,0 +1,45 @@
import path from "node:path";
const REQUIRED_SANDBOX_MODE = 0o4755;
const PERMISSION_BITS = 0o7777;
interface SandboxMetadata {
mode: number;
uid: number;
}
interface LinuxSandboxConfiguration {
platform: NodeJS.Platform;
resourcesPath: string;
statSandbox: (sandboxPath: string) => SandboxMetadata;
disableSandbox: () => void;
reportInspectionError: (error: unknown) => void;
}
function isMissingSandbox(error: unknown): boolean {
return error instanceof Error && "code" in error && error.code === "ENOENT";
}
export function configureLinuxSandbox(input: LinuxSandboxConfiguration): void {
if (input.platform !== "linux") {
return;
}
try {
const sandboxPath = path.join(input.resourcesPath, "..", "chrome-sandbox");
const sandbox = input.statSandbox(sandboxPath);
const hasUsableSandbox =
sandbox.uid === 0 && (sandbox.mode & PERMISSION_BITS) === REQUIRED_SANDBOX_MODE;
if (!hasUsableSandbox) {
input.disableSandbox();
}
} catch (error) {
if (isMissingSandbox(error)) {
input.disableSandbox();
return;
}
input.reportInspectionError(error);
}
}