@sema-agent/server 7.87.3 → 7.88.1
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/auth-bridge.js +5 -3
- package/dist/boot/device-lane.d.ts +12 -19
- package/dist/boot/device-lane.js +3 -13
- package/dist/boot/stage-01-config.js +2 -1
- package/dist/boot/stage-05-execution-env.js +1 -2
- package/dist/boot/stage-07-capability-layer.js +2 -1
- package/dist/boot/stage-09-leader.js +2 -1
- package/dist/boot/stage-10-http-server.js +4 -2
- package/dist/boot/stores.js +2 -1
- package/dist/brain.js +3 -2
- package/dist/config-catalog.js +4 -3
- package/dist/config-center/apply-effective.js +3 -2
- package/dist/config-center/http-client.d.ts +11 -2
- package/dist/config-center/http-client.js +1 -1
- package/dist/config-center/read-warnings.d.ts +1 -1
- package/dist/config-center/read-warnings.js +5 -3
- package/dist/config-center/types.d.ts +3 -1
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +12 -0
- package/dist/config-provider.d.ts +9 -1
- package/dist/config-provider.js +3 -1
- package/dist/config-types.d.ts +7 -0
- package/dist/config.js +28 -3
- package/dist/device-enrollment.d.ts +25 -4
- package/dist/device-enrollment.js +20 -2
- package/dist/device-protocol.d.ts +15 -0
- package/dist/device-protocol.js +2 -0
- package/dist/device-ws-hub.d.ts +11 -0
- package/dist/device-ws-hub.js +6 -0
- package/dist/fleet-client.js +2 -1
- package/dist/hooks/hook-llm.js +3 -2
- package/dist/http/admission.js +10 -1
- package/dist/http/device-placement.d.ts +177 -0
- package/dist/http/device-placement.js +148 -0
- package/dist/http/durable-run-leg.js +4 -0
- package/dist/http/resume-legs.js +4 -0
- package/dist/http/route-ctx.d.ts +8 -0
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/devices.d.ts +18 -8
- package/dist/http/routes/devices.js +175 -12
- package/dist/http/routes/tasks.js +10 -0
- package/dist/http/server.d.ts +13 -0
- package/dist/http/wire-types.d.ts +15 -0
- package/dist/model-route-url.d.ts +15 -0
- package/dist/model-route-url.js +19 -0
- package/dist/observability/fail-open.d.ts +11 -1
- package/dist/observability/fail-open.js +18 -2
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.js +2 -1
- package/dist/sealed-key.js +2 -1
- package/dist/security.d.ts +6 -0
- package/dist/security.js +3 -0
- package/dist/session-titler.js +4 -2
- package/dist/trace/redact.d.ts +20 -0
- package/dist/trace/redact.js +117 -26
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +979 -0
- package/package.json +8 -3
package/dist/trace/redact.d.ts
CHANGED
|
@@ -136,4 +136,24 @@ export declare function redactDeep(value: unknown): unknown;
|
|
|
136
136
|
* owner-scoped, so this preview is only ever shown to the ask's own principal (or an operator inspecting it).
|
|
137
137
|
*/
|
|
138
138
|
export declare function redactedPreview(value: unknown, cap?: number): string | null;
|
|
139
|
+
/**
|
|
140
|
+
* 🔴 **配置型 URL 上日志 / 诊断面的唯一 display-safe 铸点**(S-475 件B;`build*` = 返回纯数据的一只纯函数)。
|
|
141
|
+
*
|
|
142
|
+
* 病(7.87.2 亲核):启动日志 `listening` 行把配置里的 URL 写**原值** —— `brain: brainSummary(config)` 里的
|
|
143
|
+
* `gateway: config.gatewayBaseUrl` 与同行的 `otel: config.otel.endpoint`,logger 不脱敏。部署若把凭据放在
|
|
144
|
+
* 网关 URL 的 userinfo / query 里,密码明文落 `engine-*.log`(日志的读者面比 run 错误文本更宽:它落盘、
|
|
145
|
+
* 被采集、进工单)。同类出口不止这两处,类级机器门在 `test/config-url-display-safe.test.ts`。
|
|
146
|
+
*
|
|
147
|
+
* **内部就调 {@link redactSecrets} —— 同一条 URL 律、同一张名表、同一批记号,零第二份实现**:
|
|
148
|
+
* 本函数自己只加两件事,两件都是**配置型 URL 特有**的:
|
|
149
|
+
* ① **有效性闸**:配置里的那一位应当是一个**绝对 URL**。`new URL(raw)` 解析不动 ⇒ 这一位根本不是 URL
|
|
150
|
+
* (裸 `host:port`、相对路径、手滑的散文),自由文本的 URL 律对它没有定界依据 ⇒ 返回**占位**
|
|
151
|
+
* {@link DISPLAY_SAFE_URL_TOKEN},**不回落原值**([ref]:安全轴上的兜底必须 fail-closed 或响亮)。
|
|
152
|
+
* 成文代价:`MODEL_GATEWAY_BASEURL=127.0.0.1:8000` 这种缺 scheme 的配置在日志上变成 `«redacted:url»`
|
|
153
|
+
* —— 而它在 `fetch` 那一侧本来就是必然失败的形(WHATWG 要求绝对 URL),日志上响亮一点方向一致。
|
|
154
|
+
* ② **空串 = 未设**:里面没有凭据,原样回空串(不铸占位 —— 占位会让「没配」读成「配了但看不见」)。
|
|
155
|
+
* 解析成功后**不重建 URL**(`new URL(...).toString()` 会归一化路径/端口/大小写):保形是这条律的一半,
|
|
156
|
+
* 运维要靠 host / port / path / 非凭据参数逐字排障,所以回的是**原串经脱敏器**的结果。
|
|
157
|
+
*/
|
|
158
|
+
export declare function buildDisplaySafeUrl(raw: string): string;
|
|
139
159
|
//# sourceMappingURL=redact.d.ts.map
|
package/dist/trace/redact.js
CHANGED
|
@@ -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
|
|
598
|
-
[
|
|
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
|
|
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
|
|
1095
|
-
const
|
|
1096
|
-
if (
|
|
1097
|
-
throw new Error(
|
|
1098
|
-
return
|
|
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
|
-
|
|
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 (
|
|
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
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
|
1310
|
+
return lastAtInRun;
|
|
1243
1311
|
}
|
|
1244
|
-
function
|
|
1245
|
-
const head =
|
|
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
|
|
1250
|
-
|
|
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(
|
|
1255
|
-
head.lastIndex =
|
|
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 ===
|
|
1441
|
-
? scannerMember(row[1], order, 2, (text, json, _observe, 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
|