@wrongstack/webui 0.7.5 → 0.7.6

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.
@@ -366,7 +366,7 @@ async function startWebUI(opts = {}) {
366
366
  };
367
367
  }
368
368
  const wsToken = randomBytes(16).toString("hex");
369
- console.log(`[WebUI] WS auth token: ${wsToken}`);
369
+ console.log(`[WebUI] WS auth token: ${wsToken.slice(0, 4)}\u2026${wsToken.slice(-4)} (masked)`);
370
370
  const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
371
371
  const verifyClient = (info) => {
372
372
  const origin = info.origin;
@@ -1522,20 +1522,38 @@ async function startWebUI(opts = {}) {
1522
1522
  } else {
1523
1523
  filePath = path.join(DIST_DIR, "index.html");
1524
1524
  }
1525
- const ext = path.extname(filePath);
1525
+ const resolvedPath = path.resolve(filePath);
1526
+ const resolvedRoot = path.resolve(DIST_DIR);
1527
+ if (!resolvedPath.startsWith(resolvedRoot + path.sep) && resolvedPath !== resolvedRoot) {
1528
+ res.writeHead(403, { "Content-Type": "text/plain" });
1529
+ res.end("Forbidden");
1530
+ return;
1531
+ }
1532
+ const ext = path.extname(resolvedPath);
1526
1533
  const contentType = mimeTypes[ext] ?? "application/octet-stream";
1527
1534
  res.setHeader("Content-Type", contentType);
1535
+ res.setHeader("X-Content-Type-Options", "nosniff");
1536
+ res.setHeader("X-Frame-Options", "DENY");
1537
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
1528
1538
  if (ext === ".html") {
1529
1539
  res.setHeader("Cache-Control", "no-cache");
1540
+ res.setHeader(
1541
+ "Content-Security-Policy",
1542
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:; img-src 'self' data:; font-src 'self' data:"
1543
+ );
1530
1544
  }
1531
- const fileContent = await fs2.readFile(filePath);
1545
+ const fileContent = await fs2.readFile(resolvedPath);
1532
1546
  res.writeHead(200);
1533
1547
  res.end(fileContent);
1534
1548
  } catch (err) {
1535
1549
  if (err.code === "ENOENT") {
1536
1550
  try {
1537
1551
  const fileContent = await fs2.readFile(path.join(DIST_DIR, "index.html"));
1538
- res.writeHead(200, { "Content-Type": "text/html" });
1552
+ res.writeHead(200, {
1553
+ "Content-Type": "text/html",
1554
+ "X-Content-Type-Options": "nosniff",
1555
+ "X-Frame-Options": "DENY"
1556
+ });
1539
1557
  res.end(fileContent);
1540
1558
  } catch {
1541
1559
  res.writeHead(404);