mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 21:50:20 +00:00
refactor(gateway): share Control UI bootstrap contract and CSP
This commit is contained in:
8
src/gateway/control-ui-contract.ts
Normal file
8
src/gateway/control-ui-contract.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const CONTROL_UI_BOOTSTRAP_CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
||||
|
||||
export type ControlUiBootstrapConfig = {
|
||||
basePath: string;
|
||||
assistantName: string;
|
||||
assistantAvatar: string;
|
||||
assistantAgentId: string;
|
||||
};
|
||||
12
src/gateway/control-ui-csp.test.ts
Normal file
12
src/gateway/control-ui-csp.test.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildControlUiCspHeader } from "./control-ui-csp.js";
|
||||
|
||||
describe("buildControlUiCspHeader", () => {
|
||||
it("blocks inline scripts while allowing inline styles", () => {
|
||||
const csp = buildControlUiCspHeader();
|
||||
expect(csp).toContain("frame-ancestors 'none'");
|
||||
expect(csp).toContain("script-src 'self'");
|
||||
expect(csp).not.toContain("script-src 'self' 'unsafe-inline'");
|
||||
expect(csp).toContain("style-src 'self' 'unsafe-inline'");
|
||||
});
|
||||
});
|
||||
15
src/gateway/control-ui-csp.ts
Normal file
15
src/gateway/control-ui-csp.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function buildControlUiCspHeader(): string {
|
||||
// Control UI: block framing, block inline scripts, keep styles permissive
|
||||
// (UI uses a lot of inline style attributes in templates).
|
||||
return [
|
||||
"default-src 'self'",
|
||||
"base-uri 'none'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
"script-src 'self'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self' ws: wss:",
|
||||
].join("; ");
|
||||
}
|
||||
@@ -4,6 +4,11 @@ import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveControlUiRootSync } from "../infra/control-ui-assets.js";
|
||||
import { DEFAULT_ASSISTANT_IDENTITY, resolveAssistantIdentity } from "./assistant-identity.js";
|
||||
import {
|
||||
CONTROL_UI_BOOTSTRAP_CONFIG_PATH,
|
||||
type ControlUiBootstrapConfig,
|
||||
} from "./control-ui-contract.js";
|
||||
import { buildControlUiCspHeader } from "./control-ui-csp.js";
|
||||
import {
|
||||
buildControlUiAvatarUrl,
|
||||
CONTROL_UI_AVATAR_PREFIX,
|
||||
@@ -12,7 +17,6 @@ import {
|
||||
} from "./control-ui-shared.js";
|
||||
|
||||
const ROOT_PREFIX = "/";
|
||||
const CONTROL_UI_BOOTSTRAP_CONFIG_PATH = "/__openclaw/control-ui-config.json";
|
||||
|
||||
export type ControlUiRequestOptions = {
|
||||
basePath?: string;
|
||||
@@ -69,22 +73,7 @@ type ControlUiAvatarMeta = {
|
||||
|
||||
function applyControlUiSecurityHeaders(res: ServerResponse) {
|
||||
res.setHeader("X-Frame-Options", "DENY");
|
||||
// Control UI: block framing, block inline scripts, keep styles permissive
|
||||
// (UI uses a lot of inline style attributes in templates).
|
||||
res.setHeader(
|
||||
"Content-Security-Policy",
|
||||
[
|
||||
"default-src 'self'",
|
||||
"base-uri 'none'",
|
||||
"object-src 'none'",
|
||||
"frame-ancestors 'none'",
|
||||
"script-src 'self'",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: https:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self' ws: wss:",
|
||||
].join("; "),
|
||||
);
|
||||
res.setHeader("Content-Security-Policy", buildControlUiCspHeader());
|
||||
res.setHeader("X-Content-Type-Options", "nosniff");
|
||||
res.setHeader("Referrer-Policy", "no-referrer");
|
||||
}
|
||||
@@ -265,7 +254,7 @@ export function handleControlUiHttpRequest(
|
||||
assistantName: identity.name,
|
||||
assistantAvatar: avatarValue ?? identity.avatar,
|
||||
assistantAgentId: identity.agentId,
|
||||
});
|
||||
} satisfies ControlUiBootstrapConfig);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user