@agent-e/server 1.6.8 → 1.6.10

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,7 +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
+ function esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;').replace(/\\\\/g,'&#92;'); }
573
573
  function pad(n, w) { return String(n).padStart(w || 4, ' '); }
574
574
  function fmt(n) { return typeof n === 'number' ? n.toFixed(3) : '\u2014'; }
575
575
  function pct(n) { return typeof n === 'number' ? (n * 100).toFixed(0) + '%' : '\u2014'; }
@@ -941,7 +941,7 @@ function getDashboardHtml() {
941
941
  window._approve = function(id) {
942
942
  postJSON('/approve', { decisionId: id }).then(function(data) {
943
943
  if (data.ok) {
944
- addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-ok">\\u2705 Approved ' + id + '</span>');
944
+ addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-ok">\\u2705 Approved ' + esc(id) + '</span>');
945
945
  }
946
946
  }).catch(function() {});
947
947
  };
@@ -950,7 +950,7 @@ function getDashboardHtml() {
950
950
  var reason = prompt('Rejection reason (optional):');
951
951
  postJSON('/reject', { decisionId: id, reason: reason || undefined }).then(function(data) {
952
952
  if (data.ok) {
953
- addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + id + '</span>');
953
+ addTerminalLine('<span class="t-tick">[Advisor]</span> <span class="t-fail">\\u274c Rejected ' + esc(id) + '</span>');
954
954
  }
955
955
  }).catch(function() {});
956
956
  };
@@ -977,14 +977,30 @@ function setSecurityHeaders(res) {
977
977
  res.setHeader("X-Frame-Options", "DENY");
978
978
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
979
979
  }
980
- function setCorsHeaders(res, origin) {
980
+ function setCorsHeaders(res, allowedOrigin, requestOrigin) {
981
981
  setSecurityHeaders(res);
982
+ const origin = allowedOrigin === "*" ? "*" : requestOrigin === allowedOrigin ? allowedOrigin : allowedOrigin;
982
983
  res.setHeader("Access-Control-Allow-Origin", origin);
983
984
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
984
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
985
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
985
986
  }
986
- function json(res, status, data, origin) {
987
- setCorsHeaders(res, origin);
987
+ function sanitizeJson(obj) {
988
+ if (obj === null || typeof obj !== "object") return obj;
989
+ if (Array.isArray(obj)) return obj.map(sanitizeJson);
990
+ const clean = {};
991
+ for (const [key, val] of Object.entries(obj)) {
992
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
993
+ clean[key] = sanitizeJson(val);
994
+ }
995
+ return clean;
996
+ }
997
+ function checkAuth(req, apiKey) {
998
+ if (!apiKey) return true;
999
+ const header = req.headers["authorization"];
1000
+ return header === `Bearer ${apiKey}`;
1001
+ }
1002
+ function json(res, status, data, origin, reqOrigin) {
1003
+ setCorsHeaders(res, origin, reqOrigin);
988
1004
  res.writeHead(status, { "Content-Type": "application/json" });
989
1005
  res.end(JSON.stringify(data));
990
1006
  }
@@ -1007,6 +1023,7 @@ function readBody(req) {
1007
1023
  }
1008
1024
  function createRouteHandler(server) {
1009
1025
  const cors = server.corsOrigin;
1026
+ const apiKey = server.apiKey;
1010
1027
  return async (req, res) => {
1011
1028
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1012
1029
  const path = url.pathname;
@@ -1019,10 +1036,14 @@ function createRouteHandler(server) {
1019
1036
  }
1020
1037
  try {
1021
1038
  if (path === "/tick" && method === "POST") {
1039
+ if (!checkAuth(req, apiKey)) {
1040
+ json(res, 401, { error: "Unauthorized" }, cors);
1041
+ return;
1042
+ }
1022
1043
  const body = await readBody(req);
1023
1044
  let parsed;
1024
1045
  try {
1025
- parsed = JSON.parse(body);
1046
+ parsed = sanitizeJson(JSON.parse(body));
1026
1047
  } catch {
1027
1048
  json(res, 400, { error: "Invalid JSON" }, cors);
1028
1049
  return;
@@ -1093,10 +1114,14 @@ function createRouteHandler(server) {
1093
1114
  return;
1094
1115
  }
1095
1116
  if (path === "/config" && method === "POST") {
1117
+ if (!checkAuth(req, apiKey)) {
1118
+ json(res, 401, { error: "Unauthorized" }, cors);
1119
+ return;
1120
+ }
1096
1121
  const body = await readBody(req);
1097
1122
  let parsed;
1098
1123
  try {
1099
- parsed = JSON.parse(body);
1124
+ parsed = sanitizeJson(JSON.parse(body));
1100
1125
  } catch {
1101
1126
  json(res, 400, { error: "Invalid JSON" }, cors);
1102
1127
  return;
@@ -1113,6 +1138,7 @@ function createRouteHandler(server) {
1113
1138
  }
1114
1139
  }
1115
1140
  if (Array.isArray(config["constrain"])) {
1141
+ const validated = [];
1116
1142
  for (const c of config["constrain"]) {
1117
1143
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1118
1144
  const constraint = c;
@@ -1124,9 +1150,12 @@ function createRouteHandler(server) {
1124
1150
  json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
1125
1151
  return;
1126
1152
  }
1127
- server.constrain(constraint.param, { min: constraint.min, max: constraint.max });
1153
+ validated.push(constraint);
1128
1154
  }
1129
1155
  }
1156
+ for (const constraint of validated) {
1157
+ server.constrain(constraint.param, { min: constraint.min, max: constraint.max });
1158
+ }
1130
1159
  }
1131
1160
  if (config["mode"] === "autonomous" || config["mode"] === "advisor") {
1132
1161
  server.setMode(config["mode"]);
@@ -1148,10 +1177,14 @@ function createRouteHandler(server) {
1148
1177
  return;
1149
1178
  }
1150
1179
  if (path === "/diagnose" && method === "POST") {
1180
+ if (!checkAuth(req, apiKey)) {
1181
+ json(res, 401, { error: "Unauthorized" }, cors);
1182
+ return;
1183
+ }
1151
1184
  const body = await readBody(req);
1152
1185
  let parsed;
1153
1186
  try {
1154
- parsed = JSON.parse(body);
1187
+ parsed = sanitizeJson(JSON.parse(body));
1155
1188
  } catch {
1156
1189
  json(res, 400, { error: "Invalid JSON" }, cors);
1157
1190
  return;
@@ -1202,10 +1235,14 @@ function createRouteHandler(server) {
1202
1235
  return;
1203
1236
  }
1204
1237
  if (path === "/approve" && method === "POST") {
1238
+ if (!checkAuth(req, apiKey)) {
1239
+ json(res, 401, { error: "Unauthorized" }, cors);
1240
+ return;
1241
+ }
1205
1242
  const body = await readBody(req);
1206
1243
  let parsed;
1207
1244
  try {
1208
- parsed = JSON.parse(body);
1245
+ parsed = sanitizeJson(JSON.parse(body));
1209
1246
  } catch {
1210
1247
  json(res, 400, { error: "Invalid JSON" }, cors);
1211
1248
  return;
@@ -1241,10 +1278,14 @@ function createRouteHandler(server) {
1241
1278
  return;
1242
1279
  }
1243
1280
  if (path === "/reject" && method === "POST") {
1281
+ if (!checkAuth(req, apiKey)) {
1282
+ json(res, 401, { error: "Unauthorized" }, cors);
1283
+ return;
1284
+ }
1244
1285
  const body = await readBody(req);
1245
1286
  let parsed;
1246
1287
  try {
1247
- parsed = JSON.parse(body);
1288
+ parsed = sanitizeJson(JSON.parse(body));
1248
1289
  } catch {
1249
1290
  json(res, 400, { error: "Invalid JSON" }, cors);
1250
1291
  return;
@@ -1308,6 +1349,16 @@ function send(ws, data) {
1308
1349
  ws.send(JSON.stringify(data));
1309
1350
  }
1310
1351
  }
1352
+ function sanitizeJson2(obj) {
1353
+ if (obj === null || typeof obj !== "object") return obj;
1354
+ if (Array.isArray(obj)) return obj.map(sanitizeJson2);
1355
+ const clean = {};
1356
+ for (const [key, val] of Object.entries(obj)) {
1357
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
1358
+ clean[key] = sanitizeJson2(val);
1359
+ }
1360
+ return clean;
1361
+ }
1311
1362
  function createWebSocketHandler(httpServer, server) {
1312
1363
  const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
1313
1364
  const aliveMap = /* @__PURE__ */ new WeakMap();
@@ -1323,13 +1374,22 @@ function createWebSocketHandler(httpServer, server) {
1323
1374
  }
1324
1375
  }
1325
1376
  }, 3e4);
1326
- wss.on("connection", (ws) => {
1377
+ wss.on("connection", (ws, req) => {
1327
1378
  if (wss.clients.size > MAX_WS_CONNECTIONS) {
1328
1379
  ws.close(1013, "Server at capacity");
1329
1380
  return;
1330
1381
  }
1382
+ if (server.apiKey) {
1383
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1384
+ const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
1385
+ if (token !== server.apiKey) {
1386
+ ws.close(1008, "Unauthorized");
1387
+ return;
1388
+ }
1389
+ }
1331
1390
  console.log("[AgentE Server] Client connected");
1332
1391
  aliveMap.set(ws, true);
1392
+ let lastTickTime = 0;
1333
1393
  ws.on("pong", () => {
1334
1394
  aliveMap.set(ws, true);
1335
1395
  });
@@ -1339,7 +1399,7 @@ function createWebSocketHandler(httpServer, server) {
1339
1399
  ws.on("message", async (raw) => {
1340
1400
  let msg;
1341
1401
  try {
1342
- msg = JSON.parse(raw.toString());
1402
+ msg = sanitizeJson2(JSON.parse(raw.toString()));
1343
1403
  } catch {
1344
1404
  send(ws, { type: "error", message: "Malformed JSON" });
1345
1405
  return;
@@ -1350,6 +1410,12 @@ function createWebSocketHandler(httpServer, server) {
1350
1410
  }
1351
1411
  switch (msg.type) {
1352
1412
  case "tick": {
1413
+ const now = Date.now();
1414
+ if (now - lastTickTime < MIN_TICK_INTERVAL_MS) {
1415
+ send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
1416
+ break;
1417
+ }
1418
+ lastTickTime = now;
1353
1419
  const state = msg["state"];
1354
1420
  const events = msg["events"];
1355
1421
  if (server.validateState) {
@@ -1429,7 +1495,7 @@ function createWebSocketHandler(httpServer, server) {
1429
1495
  break;
1430
1496
  }
1431
1497
  default:
1432
- send(ws, { type: "error", message: `Unknown message type: "${msg.type}"` });
1498
+ send(ws, { type: "error", message: `Unknown message type: "${String(msg.type).slice(0, 100)}"` });
1433
1499
  }
1434
1500
  });
1435
1501
  });
@@ -1449,7 +1515,7 @@ function createWebSocketHandler(httpServer, server) {
1449
1515
  broadcast
1450
1516
  };
1451
1517
  }
1452
- var import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS;
1518
+ var import_ws, import_core2, MAX_WS_PAYLOAD, MAX_WS_CONNECTIONS, MIN_TICK_INTERVAL_MS;
1453
1519
  var init_websocket = __esm({
1454
1520
  "src/websocket.ts"() {
1455
1521
  "use strict";
@@ -1457,6 +1523,7 @@ var init_websocket = __esm({
1457
1523
  import_core2 = require("@agent-e/core");
1458
1524
  MAX_WS_PAYLOAD = 1048576;
1459
1525
  MAX_WS_CONNECTIONS = 100;
1526
+ MIN_TICK_INTERVAL_MS = 100;
1460
1527
  }
1461
1528
  });
1462
1529
 
@@ -1481,7 +1548,8 @@ var init_AgentEServer = __esm({
1481
1548
  this.startedAt = Date.now();
1482
1549
  this.wsHandle = null;
1483
1550
  this.port = config.port ?? 3100;
1484
- this.host = config.host ?? "0.0.0.0";
1551
+ this.host = config.host ?? "127.0.0.1";
1552
+ this.apiKey = config.apiKey;
1485
1553
  this.validateState = config.validateState ?? true;
1486
1554
  this.corsOrigin = config.corsOrigin ?? "http://localhost:3100";
1487
1555
  this.serveDashboard = config.serveDashboard ?? true;