@wrongstack/webui 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/entry.js +23 -11
- package/dist/server/entry.js.map +1 -1
- package/dist/server/index.js +23 -11
- package/dist/server/index.js.map +1 -1
- package/package.json +5 -5
package/dist/server/index.js
CHANGED
|
@@ -602,7 +602,6 @@ var WorktreeWebSocketHandler = class {
|
|
|
602
602
|
};
|
|
603
603
|
|
|
604
604
|
// src/server/index.ts
|
|
605
|
-
var HTML_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'";
|
|
606
605
|
async function startWebUI(opts = {}) {
|
|
607
606
|
const wsPort = opts.wsPort ?? 3457;
|
|
608
607
|
const wsHost = opts.wsHost ?? "127.0.0.1";
|
|
@@ -612,7 +611,7 @@ async function startWebUI(opts = {}) {
|
|
|
612
611
|
let config = baseConfig;
|
|
613
612
|
let configWriteLock = Promise.resolve();
|
|
614
613
|
console.log("[WebUI] Config loaded:", config.provider ?? "(none)", "/", config.model ?? "(none)");
|
|
615
|
-
if (!config.provider && config.providers && Object.keys(config.providers).length > 0) {
|
|
614
|
+
if (!config.provider && config.providers && typeof config.providers === "object" && config.providers !== null && !Array.isArray(config.providers) && Object.keys(config.providers).length > 0) {
|
|
616
615
|
const firstKey = Object.keys(config.providers)[0];
|
|
617
616
|
config = patchConfig(config, { provider: firstKey });
|
|
618
617
|
console.log("[WebUI] No active provider \u2014 auto-selected:", firstKey);
|
|
@@ -903,11 +902,12 @@ async function startWebUI(opts = {}) {
|
|
|
903
902
|
const RATE_LIMIT_MESSAGES = 60;
|
|
904
903
|
const RATE_LIMIT_WINDOW_MS = 6e4;
|
|
905
904
|
const rateLimits = /* @__PURE__ */ new Map();
|
|
906
|
-
function checkRateLimit(ws) {
|
|
905
|
+
function checkRateLimit(ws, client) {
|
|
907
906
|
const now = Date.now();
|
|
908
|
-
const
|
|
907
|
+
const key = client.sessionId ?? String(ws);
|
|
908
|
+
const limit = rateLimits.get(key);
|
|
909
909
|
if (!limit || now > limit.resetAt) {
|
|
910
|
-
rateLimits.set(
|
|
910
|
+
rateLimits.set(key, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
|
|
911
911
|
return true;
|
|
912
912
|
}
|
|
913
913
|
if (limit.count >= RATE_LIMIT_MESSAGES) return false;
|
|
@@ -1036,7 +1036,7 @@ async function startWebUI(opts = {}) {
|
|
|
1036
1036
|
autoPhaseHandler.addClient(ws);
|
|
1037
1037
|
worktreeHandler.addClient(ws);
|
|
1038
1038
|
ws.on("message", async (data) => {
|
|
1039
|
-
if (!checkRateLimit(ws)) {
|
|
1039
|
+
if (!checkRateLimit(ws, client)) {
|
|
1040
1040
|
send(ws, {
|
|
1041
1041
|
type: "error",
|
|
1042
1042
|
payload: {
|
|
@@ -1047,15 +1047,24 @@ async function startWebUI(opts = {}) {
|
|
|
1047
1047
|
return;
|
|
1048
1048
|
}
|
|
1049
1049
|
try {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1050
|
+
const rawObj = JSON.parse(data.toString());
|
|
1051
|
+
if (typeof rawObj === "object" && rawObj !== null) {
|
|
1052
|
+
const obj = rawObj;
|
|
1053
|
+
if ("__proto__" in obj || "constructor" in obj || "prototype" in obj) {
|
|
1054
|
+
send(ws, { type: "error", payload: { phase: "parse", message: "Invalid message object" } });
|
|
1055
|
+
} else {
|
|
1056
|
+
await handleMessage(ws, client, rawObj);
|
|
1057
|
+
}
|
|
1058
|
+
} else {
|
|
1059
|
+
await handleMessage(ws, client, rawObj);
|
|
1060
|
+
}
|
|
1052
1061
|
} catch (err) {
|
|
1053
1062
|
console.error("[WebUI] Failed to parse message", err);
|
|
1054
1063
|
}
|
|
1055
1064
|
});
|
|
1056
1065
|
ws.on("close", () => {
|
|
1057
1066
|
clients.delete(ws);
|
|
1058
|
-
rateLimits.delete(ws);
|
|
1067
|
+
rateLimits.delete(String(ws));
|
|
1059
1068
|
console.log("[WebUI] Client disconnected, total:", clients.size);
|
|
1060
1069
|
if (pendingConfirms.size > 0) {
|
|
1061
1070
|
for (const [id, resolve2] of pendingConfirms) {
|
|
@@ -2049,7 +2058,10 @@ async function startWebUI(opts = {}) {
|
|
|
2049
2058
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
2050
2059
|
if (ext === ".html") {
|
|
2051
2060
|
res.setHeader("Cache-Control", "no-cache");
|
|
2052
|
-
res.setHeader(
|
|
2061
|
+
res.setHeader(
|
|
2062
|
+
"Content-Security-Policy",
|
|
2063
|
+
`default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws://127.0.0.1:${wsPort} wss://127.0.0.1:${wsPort} ws://[::1]:${wsPort} wss://[::1]:${wsPort}; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`
|
|
2064
|
+
);
|
|
2053
2065
|
}
|
|
2054
2066
|
const fileContent = await fs2.readFile(resolvedPath);
|
|
2055
2067
|
res.writeHead(200);
|
|
@@ -2065,7 +2077,7 @@ async function startWebUI(opts = {}) {
|
|
|
2065
2077
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
2066
2078
|
// SPA fallback previously shipped no CSP — apply the same policy as
|
|
2067
2079
|
// the direct .html branch so deep-linked routes aren't unprotected.
|
|
2068
|
-
"Content-Security-Policy":
|
|
2080
|
+
"Content-Security-Policy": `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws://127.0.0.1:${wsPort} wss://127.0.0.1:${wsPort} ws://[::1]:${wsPort} wss://[::1]:${wsPort}; img-src 'self' data:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'`
|
|
2069
2081
|
});
|
|
2070
2082
|
res.end(fileContent);
|
|
2071
2083
|
} catch {
|