@agent-e/server 1.6.8 → 1.6.9

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/cli.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
  };
@@ -972,14 +972,30 @@ function setSecurityHeaders(res) {
972
972
  res.setHeader("X-Frame-Options", "DENY");
973
973
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
974
974
  }
975
- function setCorsHeaders(res, origin) {
975
+ function setCorsHeaders(res, allowedOrigin, requestOrigin) {
976
976
  setSecurityHeaders(res);
977
+ const origin = allowedOrigin === "*" ? "*" : requestOrigin === allowedOrigin ? allowedOrigin : allowedOrigin;
977
978
  res.setHeader("Access-Control-Allow-Origin", origin);
978
979
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
979
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
980
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
980
981
  }
981
- function json(res, status, data, origin) {
982
- setCorsHeaders(res, origin);
982
+ function sanitizeJson(obj) {
983
+ if (obj === null || typeof obj !== "object") return obj;
984
+ if (Array.isArray(obj)) return obj.map(sanitizeJson);
985
+ const clean = {};
986
+ for (const [key, val] of Object.entries(obj)) {
987
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
988
+ clean[key] = sanitizeJson(val);
989
+ }
990
+ return clean;
991
+ }
992
+ function checkAuth(req, apiKey) {
993
+ if (!apiKey) return true;
994
+ const header = req.headers["authorization"];
995
+ return header === `Bearer ${apiKey}`;
996
+ }
997
+ function json(res, status, data, origin, reqOrigin) {
998
+ setCorsHeaders(res, origin, reqOrigin);
983
999
  res.writeHead(status, { "Content-Type": "application/json" });
984
1000
  res.end(JSON.stringify(data));
985
1001
  }
@@ -1003,6 +1019,7 @@ function readBody(req) {
1003
1019
  }
1004
1020
  function createRouteHandler(server2) {
1005
1021
  const cors = server2.corsOrigin;
1022
+ const apiKey = server2.apiKey;
1006
1023
  return async (req, res) => {
1007
1024
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1008
1025
  const path = url.pathname;
@@ -1015,10 +1032,14 @@ function createRouteHandler(server2) {
1015
1032
  }
1016
1033
  try {
1017
1034
  if (path === "/tick" && method === "POST") {
1035
+ if (!checkAuth(req, apiKey)) {
1036
+ json(res, 401, { error: "Unauthorized" }, cors);
1037
+ return;
1038
+ }
1018
1039
  const body = await readBody(req);
1019
1040
  let parsed;
1020
1041
  try {
1021
- parsed = JSON.parse(body);
1042
+ parsed = sanitizeJson(JSON.parse(body));
1022
1043
  } catch {
1023
1044
  json(res, 400, { error: "Invalid JSON" }, cors);
1024
1045
  return;
@@ -1089,10 +1110,14 @@ function createRouteHandler(server2) {
1089
1110
  return;
1090
1111
  }
1091
1112
  if (path === "/config" && method === "POST") {
1113
+ if (!checkAuth(req, apiKey)) {
1114
+ json(res, 401, { error: "Unauthorized" }, cors);
1115
+ return;
1116
+ }
1092
1117
  const body = await readBody(req);
1093
1118
  let parsed;
1094
1119
  try {
1095
- parsed = JSON.parse(body);
1120
+ parsed = sanitizeJson(JSON.parse(body));
1096
1121
  } catch {
1097
1122
  json(res, 400, { error: "Invalid JSON" }, cors);
1098
1123
  return;
@@ -1109,6 +1134,7 @@ function createRouteHandler(server2) {
1109
1134
  }
1110
1135
  }
1111
1136
  if (Array.isArray(config["constrain"])) {
1137
+ const validated = [];
1112
1138
  for (const c of config["constrain"]) {
1113
1139
  if (c && typeof c === "object" && typeof c["param"] === "string" && typeof c["min"] === "number" && typeof c["max"] === "number") {
1114
1140
  const constraint = c;
@@ -1120,9 +1146,12 @@ function createRouteHandler(server2) {
1120
1146
  json(res, 400, { error: "Constraint min cannot exceed max" }, cors);
1121
1147
  return;
1122
1148
  }
1123
- server2.constrain(constraint.param, { min: constraint.min, max: constraint.max });
1149
+ validated.push(constraint);
1124
1150
  }
1125
1151
  }
1152
+ for (const constraint of validated) {
1153
+ server2.constrain(constraint.param, { min: constraint.min, max: constraint.max });
1154
+ }
1126
1155
  }
1127
1156
  if (config["mode"] === "autonomous" || config["mode"] === "advisor") {
1128
1157
  server2.setMode(config["mode"]);
@@ -1144,10 +1173,14 @@ function createRouteHandler(server2) {
1144
1173
  return;
1145
1174
  }
1146
1175
  if (path === "/diagnose" && method === "POST") {
1176
+ if (!checkAuth(req, apiKey)) {
1177
+ json(res, 401, { error: "Unauthorized" }, cors);
1178
+ return;
1179
+ }
1147
1180
  const body = await readBody(req);
1148
1181
  let parsed;
1149
1182
  try {
1150
- parsed = JSON.parse(body);
1183
+ parsed = sanitizeJson(JSON.parse(body));
1151
1184
  } catch {
1152
1185
  json(res, 400, { error: "Invalid JSON" }, cors);
1153
1186
  return;
@@ -1198,10 +1231,14 @@ function createRouteHandler(server2) {
1198
1231
  return;
1199
1232
  }
1200
1233
  if (path === "/approve" && method === "POST") {
1234
+ if (!checkAuth(req, apiKey)) {
1235
+ json(res, 401, { error: "Unauthorized" }, cors);
1236
+ return;
1237
+ }
1201
1238
  const body = await readBody(req);
1202
1239
  let parsed;
1203
1240
  try {
1204
- parsed = JSON.parse(body);
1241
+ parsed = sanitizeJson(JSON.parse(body));
1205
1242
  } catch {
1206
1243
  json(res, 400, { error: "Invalid JSON" }, cors);
1207
1244
  return;
@@ -1237,10 +1274,14 @@ function createRouteHandler(server2) {
1237
1274
  return;
1238
1275
  }
1239
1276
  if (path === "/reject" && method === "POST") {
1277
+ if (!checkAuth(req, apiKey)) {
1278
+ json(res, 401, { error: "Unauthorized" }, cors);
1279
+ return;
1280
+ }
1240
1281
  const body = await readBody(req);
1241
1282
  let parsed;
1242
1283
  try {
1243
- parsed = JSON.parse(body);
1284
+ parsed = sanitizeJson(JSON.parse(body));
1244
1285
  } catch {
1245
1286
  json(res, 400, { error: "Invalid JSON" }, cors);
1246
1287
  return;
@@ -1299,6 +1340,17 @@ function send(ws, data) {
1299
1340
  }
1300
1341
  var MAX_WS_PAYLOAD = 1048576;
1301
1342
  var MAX_WS_CONNECTIONS = 100;
1343
+ var MIN_TICK_INTERVAL_MS = 100;
1344
+ function sanitizeJson2(obj) {
1345
+ if (obj === null || typeof obj !== "object") return obj;
1346
+ if (Array.isArray(obj)) return obj.map(sanitizeJson2);
1347
+ const clean = {};
1348
+ for (const [key, val] of Object.entries(obj)) {
1349
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
1350
+ clean[key] = sanitizeJson2(val);
1351
+ }
1352
+ return clean;
1353
+ }
1302
1354
  function createWebSocketHandler(httpServer, server2) {
1303
1355
  const wss = new import_ws.WebSocketServer({ server: httpServer, maxPayload: MAX_WS_PAYLOAD });
1304
1356
  const aliveMap = /* @__PURE__ */ new WeakMap();
@@ -1314,13 +1366,22 @@ function createWebSocketHandler(httpServer, server2) {
1314
1366
  }
1315
1367
  }
1316
1368
  }, 3e4);
1317
- wss.on("connection", (ws) => {
1369
+ wss.on("connection", (ws, req) => {
1318
1370
  if (wss.clients.size > MAX_WS_CONNECTIONS) {
1319
1371
  ws.close(1013, "Server at capacity");
1320
1372
  return;
1321
1373
  }
1374
+ if (server2.apiKey) {
1375
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
1376
+ const token = url.searchParams.get("token") ?? req.headers["authorization"]?.replace("Bearer ", "");
1377
+ if (token !== server2.apiKey) {
1378
+ ws.close(1008, "Unauthorized");
1379
+ return;
1380
+ }
1381
+ }
1322
1382
  console.log("[AgentE Server] Client connected");
1323
1383
  aliveMap.set(ws, true);
1384
+ let lastTickTime = 0;
1324
1385
  ws.on("pong", () => {
1325
1386
  aliveMap.set(ws, true);
1326
1387
  });
@@ -1330,7 +1391,7 @@ function createWebSocketHandler(httpServer, server2) {
1330
1391
  ws.on("message", async (raw) => {
1331
1392
  let msg;
1332
1393
  try {
1333
- msg = JSON.parse(raw.toString());
1394
+ msg = sanitizeJson2(JSON.parse(raw.toString()));
1334
1395
  } catch {
1335
1396
  send(ws, { type: "error", message: "Malformed JSON" });
1336
1397
  return;
@@ -1341,6 +1402,12 @@ function createWebSocketHandler(httpServer, server2) {
1341
1402
  }
1342
1403
  switch (msg.type) {
1343
1404
  case "tick": {
1405
+ const now = Date.now();
1406
+ if (now - lastTickTime < MIN_TICK_INTERVAL_MS) {
1407
+ send(ws, { type: "error", message: "Rate limited \u2014 min 100ms between ticks" });
1408
+ break;
1409
+ }
1410
+ lastTickTime = now;
1344
1411
  const state = msg["state"];
1345
1412
  const events = msg["events"];
1346
1413
  if (server2.validateState) {
@@ -1420,7 +1487,7 @@ function createWebSocketHandler(httpServer, server2) {
1420
1487
  break;
1421
1488
  }
1422
1489
  default:
1423
- send(ws, { type: "error", message: `Unknown message type: "${msg.type}"` });
1490
+ send(ws, { type: "error", message: `Unknown message type: "${String(msg.type).slice(0, 100)}"` });
1424
1491
  }
1425
1492
  });
1426
1493
  });
@@ -1450,7 +1517,8 @@ var AgentEServer = class {
1450
1517
  this.startedAt = Date.now();
1451
1518
  this.wsHandle = null;
1452
1519
  this.port = config.port ?? 3100;
1453
- this.host = config.host ?? "0.0.0.0";
1520
+ this.host = config.host ?? "127.0.0.1";
1521
+ this.apiKey = config.apiKey;
1454
1522
  this.validateState = config.validateState ?? true;
1455
1523
  this.corsOrigin = config.corsOrigin ?? "http://localhost:3100";
1456
1524
  this.serveDashboard = config.serveDashboard ?? true;