mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
fix: lock the desktop backend to the app's own origin (GHSA-frx8-qqgg-7j7g)
This commit is contained in:
1 parent
cd29e1d087
commit
ad3a18224f
3 files changed
+64
-5
No files matched your search
@@ -11,6 +11,7 @@ import { authLogger } from "../../utils/logger.js";
|
||||
import { AuthManager } from "../../utils/auth-manager.js";
|
||||
import { DatabaseSaveTrigger } from "../../utils/database-save-trigger.js";
|
||||
import { parseUserAgent } from "../../utils/user-agent-parser.js";
|
||||
import { isDesktopAppOrigin } from "../../utils/cors-config.js";
|
||||
import { deleteUserAndRelatedData } from "./delete-user-data.js";
|
||||
import {
|
||||
allowsDesktopAutoSession,
|
||||
@@ -744,7 +745,11 @@ router.get("/setup-required", async (req, res) => {
|
||||
*/
|
||||
router.post("/internal/auto-session", async (req, res) => {
|
||||
try {
|
||||
if (!allowsDesktopAutoSession() || !isLoopbackRequest(req)) {
|
||||
if (
|
||||
!allowsDesktopAutoSession() ||
|
||||
!isLoopbackRequest(req) ||
|
||||
!isDesktopAppOrigin(req.headers.origin)
|
||||
) {
|
||||
authLogger.warn(
|
||||
"Rejected non-loopback attempt to access auto-session endpoint",
|
||||
{ source: req.ip },
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { Request } from "express";
|
||||
import { isCorsOriginAllowed } from "../../utils/cors-config.js";
|
||||
import {
|
||||
isCorsOriginAllowed,
|
||||
isDesktopAppOrigin,
|
||||
} from "../../utils/cors-config.js";
|
||||
|
||||
function request(headers: Record<string, string> = {}): Request {
|
||||
return {
|
||||
@@ -11,6 +14,7 @@ function request(headers: Record<string, string> = {}): Request {
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.CORS_ALLOWED_ORIGINS;
|
||||
delete process.env.ELECTRON_EMBEDDED;
|
||||
});
|
||||
|
||||
describe("isCorsOriginAllowed", () => {
|
||||
@@ -41,4 +45,41 @@ describe("isCorsOriginAllowed", () => {
|
||||
const req = request({ host: "termix.example" });
|
||||
expect(isCorsOriginAllowed(req, "https://attacker.example")).toBe(false);
|
||||
});
|
||||
|
||||
describe("embedded desktop backend", () => {
|
||||
it("rejects other websites even with no allowlist", () => {
|
||||
process.env.ELECTRON_EMBEDDED = "true";
|
||||
const req = request({ host: "127.0.0.1:30001" });
|
||||
expect(isCorsOriginAllowed(req, "https://evil.example")).toBe(false);
|
||||
});
|
||||
|
||||
it("still allows the desktop app and the dev server", () => {
|
||||
process.env.ELECTRON_EMBEDDED = "true";
|
||||
const req = request({ host: "127.0.0.1:30001" });
|
||||
expect(isCorsOriginAllowed(req, "file://")).toBe(true);
|
||||
expect(isCorsOriginAllowed(req, "http://localhost:5173")).toBe(true);
|
||||
expect(isCorsOriginAllowed(req, undefined)).toBe(true);
|
||||
});
|
||||
|
||||
it("allows an explicitly configured origin", () => {
|
||||
process.env.ELECTRON_EMBEDDED = "true";
|
||||
process.env.CORS_ALLOWED_ORIGINS = "https://portal.example";
|
||||
expect(isCorsOriginAllowed(request(), "https://portal.example")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDesktopAppOrigin", () => {
|
||||
it("accepts no origin, file:// and the dev server", () => {
|
||||
expect(isDesktopAppOrigin(undefined)).toBe(true);
|
||||
expect(isDesktopAppOrigin("file://")).toBe(true);
|
||||
expect(isDesktopAppOrigin("http://127.0.0.1:5173")).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects websites", () => {
|
||||
expect(isDesktopAppOrigin("https://evil.example")).toBe(false);
|
||||
expect(isDesktopAppOrigin("null")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -14,15 +14,28 @@ function getAllowedOrigins(): string[] {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Origins allowed to drive the embedded desktop backend: the desktop app
|
||||
* itself and the dev server. Any website open in a browser on the same
|
||||
* machine can reach loopback too, so nothing else gets in.
|
||||
*/
|
||||
export function isDesktopAppOrigin(origin: string | undefined): boolean {
|
||||
if (!origin) return true;
|
||||
return (
|
||||
DEV_ORIGINS.includes(origin) || origin.startsWith(ELECTRON_FILE_ORIGIN)
|
||||
);
|
||||
}
|
||||
|
||||
export function isCorsOriginAllowed(
|
||||
req: Request,
|
||||
origin: string | undefined,
|
||||
): boolean {
|
||||
if (!origin) return true;
|
||||
if (DEV_ORIGINS.includes(origin)) return true;
|
||||
if (origin.startsWith(ELECTRON_FILE_ORIGIN)) return true;
|
||||
if (isDesktopAppOrigin(origin)) return true;
|
||||
|
||||
const configured = getAllowedOrigins();
|
||||
if (process.env.ELECTRON_EMBEDDED === "true") {
|
||||
return configured.includes(origin) || origin === getRequestOrigin(req);
|
||||
}
|
||||
if (configured.length === 0) return true;
|
||||
if (configured.includes(origin)) return true;
|
||||
|
||||
|
||||
Reference in new issue
Block a user