@sema-agent/server 7.87.3 → 7.88.0

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.
Files changed (51) hide show
  1. package/dist/auth-bridge.js +5 -3
  2. package/dist/boot/device-lane.d.ts +12 -19
  3. package/dist/boot/device-lane.js +3 -13
  4. package/dist/boot/stage-05-execution-env.js +1 -2
  5. package/dist/boot/stage-07-capability-layer.js +2 -1
  6. package/dist/boot/stage-09-leader.js +2 -1
  7. package/dist/boot/stage-10-http-server.js +4 -2
  8. package/dist/boot/stores.js +2 -1
  9. package/dist/brain.js +3 -2
  10. package/dist/config-catalog.js +4 -3
  11. package/dist/config-center/apply-effective.js +3 -2
  12. package/dist/config-center/http-client.d.ts +11 -2
  13. package/dist/config-center/http-client.js +1 -1
  14. package/dist/config-center/read-warnings.d.ts +1 -1
  15. package/dist/config-center/read-warnings.js +5 -3
  16. package/dist/config-center/types.d.ts +3 -1
  17. package/dist/config-invariants.d.ts +2 -2
  18. package/dist/config-invariants.js +12 -0
  19. package/dist/config-provider.d.ts +9 -1
  20. package/dist/config-provider.js +3 -1
  21. package/dist/config-types.d.ts +7 -0
  22. package/dist/config.js +28 -3
  23. package/dist/device-enrollment.d.ts +25 -4
  24. package/dist/device-enrollment.js +20 -2
  25. package/dist/device-protocol.d.ts +15 -0
  26. package/dist/device-protocol.js +2 -0
  27. package/dist/device-ws-hub.d.ts +11 -0
  28. package/dist/device-ws-hub.js +6 -0
  29. package/dist/fleet-client.js +2 -1
  30. package/dist/http/admission.js +10 -1
  31. package/dist/http/device-placement.d.ts +177 -0
  32. package/dist/http/device-placement.js +148 -0
  33. package/dist/http/durable-run-leg.js +4 -0
  34. package/dist/http/resume-legs.js +4 -0
  35. package/dist/http/route-ctx.d.ts +8 -0
  36. package/dist/http/routes/capabilities.js +1 -1
  37. package/dist/http/routes/devices.d.ts +18 -8
  38. package/dist/http/routes/devices.js +175 -12
  39. package/dist/http/routes/tasks.js +10 -0
  40. package/dist/http/server.d.ts +13 -0
  41. package/dist/http/wire-types.d.ts +15 -0
  42. package/dist/model-route-url.d.ts +15 -0
  43. package/dist/model-route-url.js +19 -0
  44. package/dist/request-key-closure.js +1 -1
  45. package/dist/run-local.js +2 -1
  46. package/dist/sealed-key.js +2 -1
  47. package/dist/security.d.ts +6 -0
  48. package/dist/security.js +3 -0
  49. package/dist/trace/redact.d.ts +20 -0
  50. package/dist/trace/redact.js +117 -26
  51. package/package.json +7 -3
@@ -591,11 +591,28 @@ const keywordArm = (keywordSrc, valueClass, min, tailSrc = "") => {
591
591
  `|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${atLeast(valueClass, min)}${tailSrc})`, "g");
592
592
  return re;
593
593
  };
594
+ const CREDENTIAL_NAME_WORDS = [
595
+ "api_key", "apikey", "api-key", "key",
596
+ "token", "access_token", "refresh_token", "id_token",
597
+ "auth", "authorization",
598
+ "password", "passwd", "pwd",
599
+ "secret", "client_secret", "private_key",
600
+ "signature", "sig",
601
+ "x-amz-signature", "x-amz-security-token", "x-amz-credential",
602
+ "sas",
603
+ ];
604
+ const CREDENTIAL_NAME_ALT = (() => {
605
+ for (const w of CREDENTIAL_NAME_WORDS) {
606
+ if (!/^[a-z][a-z0-9_-]*$/.test(w))
607
+ throw new Error(`redact: credential name ${JSON.stringify(w)} must be lowercase [a-z0-9_-] (the alternation is built without escaping)`);
608
+ }
609
+ return [...CREDENTIAL_NAME_WORDS].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).join("|");
610
+ })();
594
611
  const STRUCTURAL_SECRET_PATTERNS = [
595
612
  [/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
596
613
  [/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
597
- [/:(?<=[A-Za-z][A-Za-z0-9+.-]*:)\/\//g, "$1:«redacted»@"],
598
- [/("(?:private_key|client_secret|password|api[_-]?key|secret|token)"\s*:\s*)"[^"\\]*(?:\\.[^"\\]*)*"/gi, '$1"«redacted»"'],
614
+ [/:(?<=[A-Za-z][A-Za-z0-9+.-]*:)\/\//g, "$1«redacted»"],
615
+ [new RegExp(String.raw `("(?:${CREDENTIAL_NAME_ALT})"\s*:\s*)"[^"\\]*(?:\\.[^"\\]*)*"`, "gi"), '$1"«redacted»"'],
599
616
  ];
600
617
  const PPK_CONTAINER_TOKEN = "«redacted:private-key»";
601
618
  const PEM_FRAGMENT_TOKEN = "«redacted:private-key:partial»";
@@ -1067,7 +1084,8 @@ const FRAGMENT_SECRET_PATTERNS = [
1067
1084
  [valueArm("(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-|(?:sk|rk)_(?:live|test|org)_|pypi-)", "[A-Za-z0-9_-]", 8, "needs-left-boundary"), "«redacted:key:partial»"],
1068
1085
  [valueArm("xapp-\\d-", "[A-Za-z0-9-]", 4, "needs-left-boundary"), "«redacted:slack-token:partial»"],
1069
1086
  ];
1070
- const REDACTION_TOKENS = new Set([...FULL_ARM_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
1087
+ const DISPLAY_SAFE_URL_TOKEN = "«redacted:url»";
1088
+ const REDACTION_TOKENS = new Set([...FULL_ARM_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, DISPLAY_SAFE_URL_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
1071
1089
  .flatMap((r) => r.match(/«[^»]*»/g) ?? []));
1072
1090
  {
1073
1091
  const closed = new RegExp(`^${REDACTION_TOKEN_RE_SRC}$`);
@@ -1091,11 +1109,11 @@ const FRAGMENT_ARM_TOKENS = new Set(FRAGMENT_SECRET_PATTERNS.map(([, repl]) => r
1091
1109
  }
1092
1110
  const armOfToken = (repl) => (FRAGMENT_ARM_TOKENS.has(repl) ? "fragment" : "full");
1093
1111
  const NO_HITS = [];
1094
- const URL_USERINFO_PATTERN = (() => {
1095
- const row = FULL_ARM_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
1096
- if (row === undefined)
1097
- throw new Error("redact: URL-userinfo rule missing from FULL_ARM_PATTERNS");
1098
- return row;
1112
+ const URL_CREDENTIAL_PATTERN = (() => {
1113
+ const rows = FULL_ARM_PATTERNS.filter(([, repl]) => repl === "$1«redacted»");
1114
+ if (rows.length !== 1)
1115
+ throw new Error(`redact: expected exactly one URL-credential rule (replacement "$1«redacted»") in FULL_ARM_PATTERNS, found ${rows.length}`);
1116
+ return rows[0];
1099
1117
  })();
1100
1118
  const SSH2_FENCE_PATTERN = (() => {
1101
1119
  const row = FULL_ARM_PATTERNS.find(([re]) => re.source.startsWith(SSH2_FENCE_OPEN));
@@ -1105,7 +1123,51 @@ const SSH2_FENCE_PATTERN = (() => {
1105
1123
  })();
1106
1124
  const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;
1107
1125
  const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
1126
+ const isControlCode = (c) => c < 0x20 || c === 0x7f;
1108
1127
  const TOKEN_UNIT_Y = new RegExp(REDACTION_TOKEN_RE_SRC, "y");
1128
+ const CREDENTIAL_PARAM_Y = new RegExp(`(?:${CREDENTIAL_NAME_ALT})=`, "iy");
1129
+ const CREDENTIAL_NAME_SET = new Set(CREDENTIAL_NAME_WORDS);
1130
+ const CREDENTIAL_NAME_SCAN_MAX = Math.max(...CREDENTIAL_NAME_WORDS.map((w) => w.length)) * 3 + 1;
1131
+ function decodeAsciiName(name) {
1132
+ let out = "";
1133
+ for (let i = 0; i < name.length; i++) {
1134
+ const c = name.charCodeAt(i);
1135
+ if (c !== 37) {
1136
+ out += name[i];
1137
+ continue;
1138
+ }
1139
+ const hex = name.slice(i + 1, i + 3);
1140
+ if (hex.length !== 2 || !/^[0-9A-Fa-f]{2}$/.test(hex))
1141
+ return undefined;
1142
+ const b = Number.parseInt(hex, 16);
1143
+ if (b >= 0x80)
1144
+ return undefined;
1145
+ out += String.fromCharCode(b);
1146
+ i += 2;
1147
+ }
1148
+ return out;
1149
+ }
1150
+ function credentialParamValueStart(text, p) {
1151
+ CREDENTIAL_PARAM_Y.lastIndex = p;
1152
+ if (CREDENTIAL_PARAM_Y.test(text))
1153
+ return CREDENTIAL_PARAM_Y.lastIndex;
1154
+ const limit = Math.min(text.length, p + CREDENTIAL_NAME_SCAN_MAX);
1155
+ let hasPct = false;
1156
+ for (let i = p; i < limit; i++) {
1157
+ const c = text.charCodeAt(i);
1158
+ if (c === 37) {
1159
+ hasPct = true;
1160
+ continue;
1161
+ }
1162
+ if (c !== CH_EQ)
1163
+ continue;
1164
+ if (!hasPct)
1165
+ return -1;
1166
+ const decoded = decodeAsciiName(text.slice(p, i));
1167
+ return decoded !== undefined && CREDENTIAL_NAME_SET.has(decoded.toLowerCase()) ? i + 1 : -1;
1168
+ }
1169
+ return -1;
1170
+ }
1109
1171
  function tokenUnitEnd(text, p) {
1110
1172
  TOKEN_UNIT_Y.lastIndex = p;
1111
1173
  return TOKEN_UNIT_Y.test(text) ? TOKEN_UNIT_Y.lastIndex : -1;
@@ -1195,8 +1257,10 @@ function opaqueUnitEnd(p) {
1195
1257
  scanEvidenceCursor = k;
1196
1258
  return k < st.length && st[k] === p ? scanEvidence.ends[k] : -1;
1197
1259
  }
1198
- function scanUserinfoRun(text, i, kind, json) {
1260
+ let lastAtInRun = -1;
1261
+ function scanUrlRun(text, i, kind, json) {
1199
1262
  const n = text.length;
1263
+ lastAtInRun = -1;
1200
1264
  while (i < n) {
1201
1265
  const unit = opaqueUnitEnd(i);
1202
1266
  if (unit >= 0) {
@@ -1211,11 +1275,11 @@ function scanUserinfoRun(text, i, kind, json) {
1211
1275
  }
1212
1276
  return i;
1213
1277
  }
1214
- if (c === 64 || isRegexSpace(c) || (json && c === 34))
1278
+ if (isRegexSpace(c) || (json && c === 34))
1215
1279
  return i;
1216
1280
  switch (kind) {
1217
1281
  case "plain-user":
1218
- if (c === 47 || c === 58)
1282
+ if (c === CH_AT || c === 47 || c === 58)
1219
1283
  return i;
1220
1284
  break;
1221
1285
  case "plain-pass":
@@ -1226,33 +1290,50 @@ function scanUserinfoRun(text, i, kind, json) {
1226
1290
  if (c === 58 && text.charCodeAt(i + 1) === 47 && text.charCodeAt(i + 2) === 47)
1227
1291
  return i;
1228
1292
  break;
1293
+ case "param-value":
1294
+ if (c === 38 || c === 35 || isControlCode(c))
1295
+ return i;
1296
+ break;
1229
1297
  }
1298
+ if (c === CH_AT)
1299
+ lastAtInRun = i;
1230
1300
  i += 1;
1231
1301
  }
1232
1302
  return i;
1233
1303
  }
1234
1304
  function matchUserinfoAt(text, p, json) {
1235
- const userEnd = scanUserinfoRun(text, p, "plain-user", json);
1236
- if (text.charCodeAt(userEnd) !== 58)
1237
- return null;
1238
- const keyId = userEnd > p && credentialSpansCover(scanEvidence, p, userEnd);
1239
- const passEnd = scanUserinfoRun(text, userEnd + 1, keyId ? "token-pass" : "plain-pass", json);
1240
- if (passEnd === userEnd + 1 || text.charCodeAt(passEnd) !== 64)
1305
+ const probeEnd = scanUrlRun(text, p, "plain-user", json);
1306
+ const keyId = probeEnd > p && text.charCodeAt(probeEnd) === 58 && credentialSpansCover(scanEvidence, p, probeEnd);
1307
+ scanUrlRun(text, p, keyId ? "token-pass" : "plain-pass", json);
1308
+ if (lastAtInRun <= p)
1241
1309
  return null;
1242
- return [userEnd, passEnd + 1];
1310
+ return lastAtInRun;
1243
1311
  }
1244
- function collectUrlUserinfoSpans(text, json, span) {
1245
- const head = URL_USERINFO_PATTERN[0];
1312
+ function collectUrlCredentialSpans(text, json, span) {
1313
+ const head = URL_CREDENTIAL_PATTERN[0];
1246
1314
  head.lastIndex = 0;
1247
1315
  for (let m = head.exec(text); m !== null; m = head.exec(text)) {
1248
1316
  const colon = m.index;
1249
- const hit = matchUserinfoAt(text, colon + 3, json);
1250
- if (hit === null) {
1317
+ const start = colon + 3;
1318
+ const at = matchUserinfoAt(text, start, json);
1319
+ if (at === null) {
1251
1320
  head.lastIndex = colon + 1;
1252
1321
  continue;
1253
1322
  }
1254
- span(hit[0], hit[1]);
1255
- head.lastIndex = hit[1];
1323
+ span(start, at);
1324
+ head.lastIndex = at + 1;
1325
+ }
1326
+ for (let i = 0; i < text.length; i++) {
1327
+ const c = text.charCodeAt(i);
1328
+ if (c !== 63 && c !== 38 && c !== 35)
1329
+ continue;
1330
+ const valueStart = credentialParamValueStart(text, i + 1);
1331
+ if (valueStart < 0)
1332
+ continue;
1333
+ const valueEnd = scanUrlRun(text, valueStart, "param-value", json);
1334
+ if (valueEnd > valueStart)
1335
+ span(valueStart, valueEnd);
1336
+ i = valueEnd - 1;
1256
1337
  }
1257
1338
  }
1258
1339
  let ppkScanBytes = 0;
@@ -1437,8 +1518,8 @@ const armPool = (() => {
1437
1518
  const members = [];
1438
1519
  for (const row of STRUCTURAL_SECRET_PATTERNS) {
1439
1520
  const order = members.length;
1440
- members.push(row === URL_USERINFO_PATTERN
1441
- ? scannerMember(row[1], order, 2, (text, json, _observe, span) => collectUrlUserinfoSpans(text, json, span))
1521
+ members.push(row === URL_CREDENTIAL_PATTERN
1522
+ ? scannerMember(row[1], order, 2, (text, json, _observe, span) => collectUrlCredentialSpans(text, json, span))
1442
1523
  : row === SSH2_FENCE_PATTERN
1443
1524
  ? scannerMember(row[1], order, 2, (text, _json, _observe, span) => collectSsh2FenceSpans(text, span))
1444
1525
  : regexMember(row, ARM_RANK.structural, order, true));
@@ -1631,4 +1712,14 @@ export function redactedPreview(value, cap = 2000) {
1631
1712
  s = redactSecrets(s, { json: true });
1632
1713
  return s.length > cap ? `${s.slice(0, cap)}…[truncated]` : s;
1633
1714
  }
1715
+ export function buildDisplaySafeUrl(raw) {
1716
+ if (raw === "")
1717
+ return "";
1718
+ if (!URL.canParse(raw))
1719
+ return DISPLAY_SAFE_URL_TOKEN;
1720
+ const u = new URL(raw);
1721
+ if (!u.href.startsWith(`${u.protocol}//`))
1722
+ return DISPLAY_SAFE_URL_TOKEN;
1723
+ return redactSecrets(u.href);
1724
+ }
1634
1725
  //# sourceMappingURL=redact.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.87.3",
3
+ "version": "7.88.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -14,6 +14,10 @@
14
14
  "./main": {
15
15
  "default": "./dist/main.js"
16
16
  },
17
+ "./device-protocol": {
18
+ "types": "./dist/device-protocol.d.ts",
19
+ "default": "./dist/device-protocol.js"
20
+ },
17
21
  "./package.json": "./package.json"
18
22
  },
19
23
  "files": [
@@ -54,8 +58,8 @@
54
58
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
55
59
  },
56
60
  "dependencies": {
57
- "@sema-agent/core": "7.23.2",
58
- "@sema-agent/settings-schema": "2.1.0",
61
+ "@sema-agent/core": "7.23.3",
62
+ "@sema-agent/settings-schema": "3.0.0",
59
63
  "e2b": "^2.28.0",
60
64
  "libsodium-wrappers": "^0.8.4",
61
65
  "mysql2": "^3.22.4",