@agent-e/server 1.6.0 → 1.6.3

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/index.js CHANGED
@@ -569,6 +569,7 @@ function getDashboardHtml() {
569
569
  const $app = document.getElementById('app');
570
570
 
571
571
  // \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
572
+ function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); }
572
573
  function pad(n, w) { return String(n).padStart(w || 4, ' '); }
573
574
  function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\u2014'; }
574
575
  function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '\u2014'; }
@@ -674,15 +675,15 @@ function getDashboardHtml() {
674
675
  let advisorBtns = '';
675
676
  if (isAdvisor && d.result === 'skipped_override') {
676
677
  advisorBtns = '<span class="advisor-actions">'
677
- + '<button class="advisor-btn approve" onclick="window._approve(\\'' + d.id + '\\')">[Approve]</button>'
678
- + '<button class="advisor-btn reject" onclick="window._reject(\\'' + d.id + '\\')">[Reject]</button>'
678
+ + '<button class="advisor-btn approve" onclick="window._approve(\\'' + esc(d.id) + '\\')">[Approve]</button>'
679
+ + '<button class="advisor-btn reject" onclick="window._reject(\\'' + esc(d.id) + '\\')">[Reject]</button>'
679
680
  + '</span>';
680
681
  }
681
682
 
682
683
  return '<span class="t-tick">[Tick ' + pad(d.tick) + ']</span> '
683
684
  + resultIcon
684
- + '<span class="t-principle">[' + (principle.id || '?') + '] ' + (principle.name || '') + ':</span> '
685
- + '<span class="t-param">' + (plan.parameter || '\u2014') + ' </span>'
685
+ + '<span class="t-principle">[' + esc(principle.id || '?') + '] ' + esc(principle.name || '') + ':</span> '
686
+ + '<span class="t-param">' + esc(plan.parameter || '\u2014') + ' </span>'
686
687
  + '<span class="t-old">' + fmt(plan.currentValue) + '</span>'
687
688
  + '<span class="t-arrow"> \\u2192 </span>'
688
689
  + '<span class="t-new">' + fmt(plan.targetValue) + '</span>'
@@ -706,8 +707,8 @@ function getDashboardHtml() {
706
707
  return '<div class="alert-card">'
707
708
  + '<span class="alert-severity ' + sc + '">' + sev + '/10</span>'
708
709
  + '<div class="alert-body">'
709
- + '<div class="alert-principle">[' + pid + '] ' + name + '</div>'
710
- + '<div class="alert-reason">' + reason + '</div>'
710
+ + '<div class="alert-principle">[' + esc(pid) + '] ' + esc(name) + '</div>'
711
+ + '<div class="alert-reason">' + esc(reason) + '</div>'
711
712
  + '</div></div>';
712
713
  }).join('');
713
714
  }
@@ -735,10 +736,10 @@ function getDashboardHtml() {
735
736
  $violationsBody.innerHTML = sorted.map(function(v) {
736
737
  return '<tr>'
737
738
  + '<td>' + v.tick + '</td>'
738
- + '<td style="color:var(--text-primary);font-family:var(--font-sans)">' + v.principle + '</td>'
739
+ + '<td style="color:var(--text-primary);font-family:var(--font-sans)">' + esc(v.principle) + '</td>'
739
740
  + '<td><span class="alert-severity ' + sevClass(v.severity) + '">' + v.severity + '</span></td>'
740
- + '<td>' + v.parameter + '</td>'
741
- + '<td>' + v.result + '</td>'
741
+ + '<td>' + esc(v.parameter) + '</td>'
742
+ + '<td>' + esc(v.result) + '</td>'
742
743
  + '</tr>';
743
744
  }).join('');
744
745
  }
@@ -764,7 +765,7 @@ function getDashboardHtml() {
764
765
  $personaBars.innerHTML = entries.map(function(e) {
765
766
  const pctVal = total > 0 ? (e[1] / total * 100) : 0;
766
767
  return '<div class="persona-row">'
767
- + '<div class="persona-label">' + e[0] + '</div>'
768
+ + '<div class="persona-label">' + esc(e[0]) + '</div>'
768
769
  + '<div class="persona-bar-track"><div class="persona-bar-fill" style="width:' + pctVal + '%"></div></div>'
769
770
  + '<div class="persona-pct">' + pctVal.toFixed(0) + '%</div>'
770
771
  + '</div>';
@@ -777,12 +778,10 @@ function getDashboardHtml() {
777
778
  $registryList.innerHTML = '<div class="empty-state">No parameters registered</div>';
778
779
  return;
779
780
  }
780
- // Show unique parameters from principles
781
- const params = new Set();
782
781
  $registryList.innerHTML = principles.slice(0, 30).map(function(p) {
783
782
  return '<div class="registry-item">'
784
- + '<span class="registry-key">[' + p.id + ']</span>'
785
- + '<span class="registry-val">' + p.name + '</span>'
783
+ + '<span class="registry-key">[' + esc(p.id) + ']</span>'
784
+ + '<span class="registry-val">' + esc(p.name) + '</span>'
786
785
  + '</div>';
787
786
  }).join('');
788
787
  }
@@ -973,7 +972,13 @@ var init_dashboard = __esm({
973
972
  });
974
973
 
975
974
  // src/routes.ts
975
+ function setSecurityHeaders(res) {
976
+ res.setHeader("X-Content-Type-Options", "nosniff");
977
+ res.setHeader("X-Frame-Options", "DENY");
978
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
979
+ }
976
980
  function setCorsHeaders(res, origin) {
981
+ setSecurityHeaders(res);
977
982
  res.setHeader("Access-Control-Allow-Origin", origin);
978
983
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
979
984
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
@@ -1029,21 +1034,19 @@ function createRouteHandler(server) {
1029
1034
  const payload = parsed;
1030
1035
  const state = payload["state"] ?? parsed;
1031
1036
  const events = payload["events"];
1032
- if (server.validateState) {
1033
- const validation = (0, import_core.validateEconomyState)(state);
1034
- if (!validation.valid) {
1035
- json(res, 400, {
1036
- error: "invalid_state",
1037
- validationErrors: validation.errors
1038
- }, cors);
1039
- return;
1040
- }
1037
+ const validation = server.validateState ? (0, import_core.validateEconomyState)(state) : null;
1038
+ if (validation && !validation.valid) {
1039
+ json(res, 400, {
1040
+ error: "invalid_state",
1041
+ validationErrors: validation.errors
1042
+ }, cors);
1043
+ return;
1041
1044
  }
1042
1045
  const result = await server.processTick(
1043
1046
  state,
1044
1047
  Array.isArray(events) ? events : void 0
1045
1048
  );
1046
- const warnings = server.validateState ? (0, import_core.validateEconomyState)(state).warnings : [];
1049
+ const warnings = validation?.warnings ?? [];
1047
1050
  json(res, 200, {
1048
1051
  adjustments: result.adjustments,
1049
1052
  alerts: result.alerts.map((a) => ({
@@ -1071,12 +1074,18 @@ function createRouteHandler(server) {
1071
1074
  return;
1072
1075
  }
1073
1076
  if (path === "/decisions" && method === "GET") {
1074
- const limit = parseInt(url.searchParams.get("limit") ?? "100", 10);
1075
- const since = url.searchParams.get("since");
1077
+ const rawLimit = parseInt(url.searchParams.get("limit") ?? "100", 10);
1078
+ const limit = Math.min(Math.max(isNaN(rawLimit) ? 100 : rawLimit, 1), 1e3);
1079
+ const sinceParam = url.searchParams.get("since");
1076
1080
  const agentE = server.getAgentE();
1077
1081
  let decisions;
1078
- if (since) {
1079
- decisions = agentE.getDecisions({ since: parseInt(since, 10) });
1082
+ if (sinceParam) {
1083
+ const since = parseInt(sinceParam, 10);
1084
+ if (isNaN(since)) {
1085
+ json(res, 400, { error: 'Invalid "since" parameter \u2014 must be a number' }, cors);
1086
+ return;
1087
+ }
1088
+ decisions = agentE.getDecisions({ since });
1080
1089
  } else {
1081
1090
  decisions = agentE.log.latest(limit);
1082
1091
  }
@@ -1107,6 +1116,14 @@ function createRouteHandler(server) {
1107
1116
  for (const c of config["constrain"]) {
1108
1117
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1109
1118
  const constraint = c;
1119
+ if (!isFinite(constraint.min) || !isFinite(constraint.max)) {
1120
+ json(res, 400, { error: "Constraint bounds must be finite numbers" }, cors);
1121
+ return;
1122
+ }
1123
+ if (constraint.min > constraint.max) {
1124
+ json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
1125
+ return;
1126
+ }
1110
1127
  server.constrain(constraint.param, { min: constraint.min, max: constraint.max });
1111
1128
  }
1112
1129
  }
@@ -1163,6 +1180,7 @@ function createRouteHandler(server) {
1163
1180
  }
1164
1181
  if (path === "/" && method === "GET" && server.serveDashboard) {
1165
1182
  setCorsHeaders(res, cors);
1183
+ res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self' ws: wss:; img-src 'self' data:");
1166
1184
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1167
1185
  res.end(getDashboardHtml());
1168
1186
  return;
@@ -1290,7 +1308,7 @@ function send(ws, data) {
1290
1308
  }
1291
1309
  }
1292
1310
  function createWebSocketHandler(httpServer, server) {
1293
- const wss = new import_ws.WebSocketServer({ server: httpServer });
1311
+ const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
1294
1312
  const aliveMap = /* @__PURE__ */ new WeakMap();
1295
1313
  const heartbeatInterval = setInterval(() => {
1296
1314
  for (const ws of wss.clients) {
@@ -1305,6 +1323,10 @@ function createWebSocketHandler(httpServer, server) {
1305
1323
  }
1306
1324
  }, 3e4);
1307
1325
  wss.on("connection", (ws) => {
1326
+ if (wss.clients.size > MAX_WS_CONNECTIONS) {
1327
+ ws.close(1013, "Server at capacity");
1328
+ return;
1329
+ }
1308
1330
  console.log("[AgentE Server] Client connected");
1309
1331
  aliveMap.set(ws, true);
1310
1332
  ws.on("pong", () => {
@@ -1426,12 +1448,14 @@ function createWebSocketHandler(httpServer, server) {
1426
1448
  broadcast
1427
1449
  };
1428
1450
  }
1429
- var import_ws, import_core2;
1451
+ var import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS;
1430
1452
  var init_websocket = __esm({
1431
1453
  "src/websocket.ts"() {
1432
1454
  "use strict";
1433
1455
  import_ws = require("ws");
1434
1456
  import_core2 = require("@agent-e/core");
1457
+ MAX_WS_PAYLOAD = 1048576;
1458
+ MAX_WS_CONNECTIONS = 100;
1435
1459
  }
1436
1460
  });
1437
1461
 
@@ -1458,7 +1482,7 @@ var init_AgentEServer = __esm({
1458
1482
  this.port = config.port ?? 3100;
1459
1483
  this.host = config.host ?? "0.0.0.0";
1460
1484
  this.validateState = config.validateState ?? true;
1461
- this.corsOrigin = config.corsOrigin ?? "*";
1485
+ this.corsOrigin = config.corsOrigin ?? "http://localhost:3100";
1462
1486
  this.serveDashboard = config.serveDashboard ?? true;
1463
1487
  const adapter = {
1464
1488
  getState: () => {