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