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