@sema-agent/server 7.140.0 → 7.142.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 (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -888,6 +888,28 @@ const CREDENTIAL_NAME_ALT = (() => {
888
888
  return [...CREDENTIAL_PARAM_NAMES].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).join("|");
889
889
  })();
890
890
  const CREDENTIAL_NAME_MAX = Math.max(...CREDENTIAL_PARAM_NAMES.map((w) => w.length));
891
+ const JSON_FIELD_QUOTE_FORMS = ['"', '\\"', '\\\\\\"'];
892
+ for (let d = 0; d < JSON_FIELD_QUOTE_FORMS.length; d++) {
893
+ if (JSON_FIELD_QUOTE_FORMS[d] !== "\\".repeat(2 ** d - 1) + '"')
894
+ throw new Error(`redact: JSON field quote form ${d} must be ${2 ** d - 1} backslashes + a quote`);
895
+ }
896
+ function buildJsonSecretFieldSource() {
897
+ const lt = String.raw `\n\r\u2028\u2029`;
898
+ const l = `[${lt}]`;
899
+ let q = '"';
900
+ let b = String.raw `\\`;
901
+ let o = `[^"\\\\${lt}]`;
902
+ const alts = [];
903
+ for (let d = 0; d < JSON_FIELD_QUOTE_FORMS.length; d++) {
904
+ if (d > 0) {
905
+ o = `(?:${o}|${b}${o})`;
906
+ q = b + q;
907
+ b = b + b;
908
+ }
909
+ alts.push(String.raw `(${q}(?:${CREDENTIAL_NAME_ALT})${q}\s*:\s*)${q}(?:${o}|${l}|${b}(?:${o}|${b}|${q}))*${q}`);
910
+ }
911
+ return alts.join("|");
912
+ }
891
913
  const asciiLower = (s) => s.replace(/[A-Z]/g, (c) => c.toLowerCase());
892
914
  function credentialNameAnchor(name) {
893
915
  return CREDENTIAL_ANCHOR_OF.get(asciiLower(name));
@@ -897,7 +919,7 @@ const STRUCTURAL_SECRET_PATTERNS = [
897
919
  [/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
898
920
  [/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
899
921
  [/:(?<=[A-Za-z][A-Za-z0-9+.-]*:)\/\//g, "$1«redacted»"],
900
- [new RegExp(String.raw `("(?:${CREDENTIAL_NAME_ALT})"\s*:\s*)"[^"\\]*(?:\\.[^"\\]*)*"`, "gi"), '$1"«redacted»"'],
922
+ [new RegExp(buildJsonSecretFieldSource(), "gi"), '$1"«redacted»"'],
901
923
  ];
902
924
  const PPK_CONTAINER_TOKEN = "«redacted:private-key»";
903
925
  const PEM_FRAGMENT_TOKEN = "«redacted:private-key:partial»";
@@ -1094,6 +1116,7 @@ function parsePemBody(scan, s, i, limit) {
1094
1116
  function rightmostPemRunEnd(scan, s, from, to) {
1095
1117
  let best = -1;
1096
1118
  let runStart = -1;
1119
+ let plainBackslashTill = -1;
1097
1120
  for (let p = from; p <= to; p++) {
1098
1121
  if (p < to) {
1099
1122
  const unit = opaqueUnitEnd(scan, p);
@@ -1106,6 +1129,20 @@ function rightmostPemRunEnd(scan, s, from, to) {
1106
1129
  p = unit - 1;
1107
1130
  continue;
1108
1131
  }
1132
+ if (p >= plainBackslashTill && s.charCodeAt(p) === CH_BACKSLASH) {
1133
+ const j = pemEscapeLetterAt(s, p, to);
1134
+ if (j >= 0) {
1135
+ if (runStart >= 0 && p - runStart >= PEM_BODY_RUN_MIN)
1136
+ best = p;
1137
+ runStart = -1;
1138
+ p = j;
1139
+ continue;
1140
+ }
1141
+ let k = p + 1;
1142
+ while (k < to && s.charCodeAt(k) === CH_BACKSLASH)
1143
+ k += 1;
1144
+ plainBackslashTill = k;
1145
+ }
1109
1146
  }
1110
1147
  if (p < to && isBase64Code(s.charCodeAt(p))) {
1111
1148
  if (runStart < 0)
@@ -1153,22 +1190,22 @@ function findPemFence(scan, text, from) {
1153
1190
  if (close < 0)
1154
1191
  return undefined;
1155
1192
  from = labelFrom;
1156
- if (inOpaqueUnit(scan, fence))
1157
- continue;
1158
1193
  if (!isPemFenceLabel(text, labelFrom, close))
1159
1194
  continue;
1160
- return { fence, after: close + PEM_FENCE_CLOSE.length };
1195
+ const after = close + PEM_FENCE_CLOSE.length;
1196
+ if (anchorHeld(scan, fence, after))
1197
+ continue;
1198
+ return { fence, after };
1161
1199
  }
1162
1200
  }
1163
1201
  const SSH2_FENCE_OPEN = "---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----";
1164
1202
  const SSH2_FENCE_CLOSE = "---- END SSH2 ENCRYPTED PRIVATE KEY ----";
1165
1203
  const SSH2_BODY_MAX = 8192;
1166
- function unitCountWithin(scan, from, to, max) {
1204
+ function unitCountWithin(scan, from, to, max, t = scan.table) {
1167
1205
  if (to - from <= max)
1168
1206
  return true;
1169
1207
  noteEvidenceRead(scan, from);
1170
1208
  noteEvidenceRead(scan, to);
1171
- const t = scan.table;
1172
1209
  const lo = firstCredentialSpanFrom(t, from);
1173
1210
  let a = lo;
1174
1211
  let b = t.ends.length;
@@ -1200,7 +1237,7 @@ export function unitCountWithinForTest(table, from, to, max) {
1200
1237
  const t = { starts: Int32Array.from(table, (x) => x[0]), ends: Int32Array.from(table, (x) => x[1]) };
1201
1238
  return unitCountWithin(buildForensicScan(t, 1), from, to, max);
1202
1239
  }
1203
- function collectSsh2FenceSpans(scan, text, span, isHead) {
1240
+ function collectSsh2FenceSpans(scan, text, json, span, isHead) {
1204
1241
  let cachedFrom = 0;
1205
1242
  let cachedAt;
1206
1243
  const nextClose = (from) => {
@@ -1217,29 +1254,78 @@ function collectSsh2FenceSpans(scan, text, span, isHead) {
1217
1254
  cachedAt = p;
1218
1255
  return p;
1219
1256
  };
1257
+ let freeFrom = 0;
1258
+ let freeAt;
1259
+ const freeClose = (from) => {
1260
+ if (freeAt !== undefined && freeFrom <= from && (freeAt < 0 || freeAt >= from))
1261
+ return freeAt;
1262
+ let p = text.indexOf(SSH2_FENCE_CLOSE, from);
1263
+ ssh2ScanBytes += (p < 0 ? text.length : p) - from;
1264
+ while (p >= 0 && anchorHeld(scan, p, p + 1)) {
1265
+ const q = text.indexOf(SSH2_FENCE_CLOSE, p + 1);
1266
+ ssh2ScanBytes += (q < 0 ? text.length : q) - (p + 1);
1267
+ p = q;
1268
+ }
1269
+ freeFrom = from;
1270
+ freeAt = p;
1271
+ return p;
1272
+ };
1273
+ const truncatedEnd = createTruncatedBlockEnd(scan, text, json, freeClose);
1274
+ let openFrom = 0;
1275
+ let openAt;
1276
+ const nextFreeOpen = (from) => {
1277
+ if (openAt !== undefined && openFrom <= from && (openAt < 0 || openAt >= from))
1278
+ return openAt;
1279
+ let p = text.indexOf(SSH2_FENCE_OPEN, from);
1280
+ ssh2ScanBytes += (p < 0 ? text.length : p) - from;
1281
+ while (p >= 0 && anchorHeld(scan, p, p + SSH2_FENCE_OPEN.length)) {
1282
+ const q = text.indexOf(SSH2_FENCE_OPEN, p + 1);
1283
+ ssh2ScanBytes += (q < 0 ? text.length : q) - (p + 1);
1284
+ p = q;
1285
+ }
1286
+ openFrom = from;
1287
+ openAt = p;
1288
+ return p;
1289
+ };
1220
1290
  for (let from = 0;;) {
1221
1291
  const fence = text.indexOf(SSH2_FENCE_OPEN, from);
1222
1292
  if (fence < 0)
1223
1293
  return;
1224
- if (inOpaqueUnit(scan, fence)) {
1294
+ const bodyFrom = fence + SSH2_FENCE_OPEN.length;
1295
+ if (anchorHeld(scan, fence, bodyFrom)) {
1225
1296
  from = fence + 1;
1226
1297
  continue;
1227
1298
  }
1228
- const bodyFrom = fence + SSH2_FENCE_OPEN.length;
1229
1299
  if (isHead && unitCountWithin(scan, bodyFrom, text.length, SSH2_BODY_MAX)) {
1230
1300
  span(fence, text.length, true);
1231
1301
  return;
1232
1302
  }
1303
+ let resume = fence + 1;
1304
+ if (!isHead) {
1305
+ const open = nextFreeOpen(bodyFrom);
1306
+ const limit = open < 0 ? text.length : open;
1307
+ const t = truncatedEnd(bodyFrom, limit);
1308
+ const bodyEnd = t < 0 ? -1 : armorBodyEnd(scan, text, json, bodyFrom, limit, false);
1309
+ const end = bodyEnd < 0 ? -1 : Math.max(bodyEnd, t);
1310
+ if (end >= 0 && unitCountWithin(scan, bodyFrom, end, SSH2_BODY_MAX, othersOf(scan))) {
1311
+ span(fence, end);
1312
+ resume = end;
1313
+ }
1314
+ }
1233
1315
  const close = nextClose(bodyFrom);
1234
- if (close < 0)
1235
- return;
1316
+ if (close < 0) {
1317
+ if (isHead)
1318
+ return;
1319
+ from = resume;
1320
+ continue;
1321
+ }
1236
1322
  if (!unitCountWithin(scan, bodyFrom, close, SSH2_BODY_MAX)) {
1237
- from = fence + 1;
1323
+ from = resume;
1238
1324
  continue;
1239
1325
  }
1240
1326
  const end = close + SSH2_FENCE_CLOSE.length;
1241
1327
  span(fence, end);
1242
- from = scanResumeAfterHit(fence, end, SSH2_FENCE_OPEN.length);
1328
+ from = Math.max(resume, scanResumeAfterHit(fence, end, SSH2_FENCE_OPEN.length));
1243
1329
  }
1244
1330
  }
1245
1331
  const PEM_END_OPEN = "-----END";
@@ -1280,12 +1366,12 @@ function collectPemFenceSpans(scan, text, span, isHead) {
1280
1366
  const fence = text.indexOf(PEM_FENCE_OPEN, from);
1281
1367
  if (fence < 0)
1282
1368
  return;
1283
- if (inOpaqueUnit(scan, fence)) {
1369
+ const bodyFrom = pemFenceLineEnd(text, fence + PEM_FENCE_OPEN.length);
1370
+ if (bodyFrom === PEM_LINE_INVALID) {
1284
1371
  from = fence + 1;
1285
1372
  continue;
1286
1373
  }
1287
- const bodyFrom = pemFenceLineEnd(text, fence + PEM_FENCE_OPEN.length);
1288
- if (bodyFrom === PEM_LINE_INVALID) {
1374
+ if (anchorHeld(scan, fence, bodyFrom === PEM_LINE_OPEN ? text.length : bodyFrom)) {
1289
1375
  from = fence + 1;
1290
1376
  continue;
1291
1377
  }
@@ -1304,35 +1390,80 @@ function collectPemFenceSpans(scan, text, span, isHead) {
1304
1390
  from = scanResumeAfterHit(fence, endAfter, PEM_FENCE_OPEN.length);
1305
1391
  }
1306
1392
  }
1307
- function collectPemFragmentSpans(scan, text, json, span) {
1393
+ function truncationWindowEnd(text, from, json) {
1394
+ if (!json)
1395
+ return text.length;
1396
+ for (let i = from; i < text.length; i++) {
1397
+ const c = text.charCodeAt(i);
1398
+ if (c === CH_BACKSLASH) {
1399
+ i += 1;
1400
+ continue;
1401
+ }
1402
+ if (c === CH_QUOTE)
1403
+ return i;
1404
+ }
1405
+ return text.length;
1406
+ }
1407
+ function armorBodyEnd(scan, text, json, from, limit, allowBare) {
1408
+ let pos = from;
1409
+ let bare = allowBare;
1410
+ let hitLimit = false;
1411
+ let rightmost = -1;
1412
+ while (parsePemHeaderLine(scan, text, pos, limit, bare, json)) {
1413
+ const e = rightmostPemRunEnd(scan, text, scan.hdr.search, scan.hdr.contentEnd);
1414
+ if (e > rightmost)
1415
+ rightmost = e;
1416
+ pos = scan.hdr.next;
1417
+ bare = false;
1418
+ if (scan.hdr.hitLimit) {
1419
+ hitLimit = true;
1420
+ break;
1421
+ }
1422
+ if (scan.hdr.jsonEnd)
1423
+ break;
1424
+ }
1425
+ const bodyEnd = parsePemBody(scan, text, pos, limit);
1426
+ scan.hdr.hitLimit = hitLimit;
1427
+ return bodyEnd >= 0 ? bodyEnd : rightmost;
1428
+ }
1429
+ function createTruncatedBlockEnd(scan, text, json, closeAt) {
1430
+ let winFrom = -1;
1431
+ let winAt = -1;
1432
+ return (from, limit) => {
1433
+ if (!(winFrom >= 0 && from >= winFrom && from <= winAt)) {
1434
+ winFrom = from;
1435
+ winAt = truncationWindowEnd(text, from, json);
1436
+ }
1437
+ const close = closeAt(from);
1438
+ if (close >= 0 && close < winAt)
1439
+ return -1;
1440
+ return rightmostPemRunEnd(scan, text, from, Math.min(limit, winAt));
1441
+ };
1442
+ }
1443
+ function collectPemFragmentSpans(scan, text, json, span, isHead) {
1308
1444
  let cur = findPemFence(scan, text, 0);
1309
1445
  let inherited = false;
1446
+ let closeFrom = -1;
1447
+ let closeAt = -1;
1448
+ const freeClose = (from) => {
1449
+ if (closeFrom >= 0 && from >= closeFrom && (closeAt < 0 || closeAt >= from))
1450
+ return closeAt;
1451
+ let b = text.indexOf(PEM_END_OPEN, from);
1452
+ while (b >= 0 && (pemFenceLineEnd(text, b + PEM_END_OPEN.length) < 0 || anchorHeld(scan, b, b + 1)))
1453
+ b = text.indexOf(PEM_END_OPEN, b + 1);
1454
+ closeFrom = from;
1455
+ closeAt = b;
1456
+ return b;
1457
+ };
1458
+ const truncatedEnd = createTruncatedBlockEnd(scan, text, json, freeClose);
1310
1459
  while (cur !== undefined) {
1311
1460
  const next = findPemFence(scan, text, cur.after);
1312
1461
  const limit = next === undefined ? text.length : next.fence;
1313
- let pos = cur.after;
1314
- let allowBare = inherited;
1315
- let hitLimit = false;
1316
- let rightmost = -1;
1317
- while (parsePemHeaderLine(scan, text, pos, limit, allowBare, json)) {
1318
- const e = rightmostPemRunEnd(scan, text, scan.hdr.search, scan.hdr.contentEnd);
1319
- if (e > rightmost)
1320
- rightmost = e;
1321
- pos = scan.hdr.next;
1322
- allowBare = false;
1323
- if (scan.hdr.hitLimit) {
1324
- hitLimit = true;
1325
- break;
1326
- }
1327
- if (scan.hdr.jsonEnd)
1328
- break;
1329
- }
1330
- inherited = hitLimit;
1331
- let bodyEnd = parsePemBody(scan, text, pos, limit);
1332
- if (bodyEnd < 0)
1333
- bodyEnd = rightmost;
1334
- if (bodyEnd >= 0)
1335
- span(cur.fence, bodyEnd);
1462
+ const bodyEnd = armorBodyEnd(scan, text, json, cur.after, limit, inherited);
1463
+ inherited = scan.hdr.hitLimit;
1464
+ const end = isHead || bodyEnd < 0 ? bodyEnd : Math.max(bodyEnd, truncatedEnd(cur.after, limit));
1465
+ if (end >= 0)
1466
+ span(cur.fence, end);
1336
1467
  cur = next;
1337
1468
  }
1338
1469
  }
@@ -1438,10 +1569,6 @@ function collectHeadlessPpkSpans(scan, text, span, isHead) {
1438
1569
  const at = text.indexOf(KEY, search);
1439
1570
  if (at === -1)
1440
1571
  break;
1441
- if (inOpaqueUnit(scan, at)) {
1442
- search = at + KEY.length;
1443
- continue;
1444
- }
1445
1572
  let p = at + KEY.length;
1446
1573
  const d0 = p;
1447
1574
  while (p < n && opaqueUnitEnd(scan, p) < 0 && text.charCodeAt(p) >= 48 && text.charCodeAt(p) <= 57)
@@ -1450,6 +1577,10 @@ function collectHeadlessPpkSpans(scan, text, span, isHead) {
1450
1577
  search = at + KEY.length;
1451
1578
  continue;
1452
1579
  }
1580
+ if (anchorHeld(scan, at, p)) {
1581
+ search = at + KEY.length;
1582
+ continue;
1583
+ }
1453
1584
  let end = -1;
1454
1585
  let open = false;
1455
1586
  for (let from = p;;) {
@@ -1548,6 +1679,14 @@ const JSON_SECRET_FIELD_PATTERN = (() => {
1548
1679
  throw new Error(`redact: expected exactly one JSON secret-field rule (replacement '$1"«redacted»"') in FULL_ARM_PATTERNS, found ${rows.length}`);
1549
1680
  return rows[0];
1550
1681
  })();
1682
+ const JSON_FIELD_VALUE_REPLS = (() => {
1683
+ const [plain] = JSON_FIELD_QUOTE_FORMS;
1684
+ const repl = JSON_SECRET_FIELD_PATTERN[1];
1685
+ if (!repl.startsWith("$1" + plain) || !repl.endsWith(plain) || repl.length < 2 + 2 * plain.length)
1686
+ throw new Error(`redact: the JSON secret-field replacement must be $1 + a quoted mark, got ${JSON.stringify(repl)}`);
1687
+ const mark = repl.slice(2 + plain.length, repl.length - plain.length);
1688
+ return JSON_FIELD_QUOTE_FORMS.map((f) => f + mark + f);
1689
+ })();
1551
1690
  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;
1552
1691
  const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
1553
1692
  const isControlCode = (c) => c < 0x20 || c === 0x7f;
@@ -1755,6 +1894,7 @@ const FOOT_NONE = 0x7fffffff;
1755
1894
  function buildForensicScan(table, armCount) {
1756
1895
  return {
1757
1896
  table,
1897
+ others: undefined,
1758
1898
  cursor: 0,
1759
1899
  dirty: undefined,
1760
1900
  footLo: FOOT_NONE,
@@ -1768,8 +1908,9 @@ function buildForensicScan(table, armCount) {
1768
1908
  hdr: { search: 0, contentEnd: 0, next: 0, hitLimit: false, jsonEnd: false },
1769
1909
  };
1770
1910
  }
1771
- function useEvidenceView(scan, table, dirty) {
1911
+ function useEvidenceView(scan, table, dirty, others) {
1772
1912
  scan.table = table;
1913
+ scan.others = others;
1773
1914
  scan.cursor = 0;
1774
1915
  scan.dirty = dirty;
1775
1916
  }
@@ -1803,6 +1944,22 @@ function inOpaqueUnit(scan, p) {
1803
1944
  const k = firstCredentialSpanFrom(t, p) - 1;
1804
1945
  return k >= 0 && st[k] < p && t.ends[k] > p;
1805
1946
  }
1947
+ function othersOf(scan) {
1948
+ const t = scan.others;
1949
+ if (t === undefined)
1950
+ throw new Error("redact: a structural-anchor question was asked by a forensic arm that declares no credential family — it has no other-families evidence to read");
1951
+ return t;
1952
+ }
1953
+ function anchorHeld(scan, p, lineEnd) {
1954
+ const t = othersOf(scan);
1955
+ noteEvidenceRead(scan, p);
1956
+ noteEvidenceRead(scan, lineEnd - 1);
1957
+ const st = t.starts;
1958
+ if (st.length === 0)
1959
+ return false;
1960
+ const k = firstCredentialSpanFrom(t, p) - 1;
1961
+ return k >= 0 && st[k] < p && t.ends[k] >= lineEnd;
1962
+ }
1806
1963
  function opaqueUnitEnd(scan, p) {
1807
1964
  noteEvidenceRead(scan, p);
1808
1965
  return opaqueUnitEndAt(scan, p);
@@ -2001,21 +2158,22 @@ function collectUrlCredentialSpans(scan, text, json, span, isHead) {
2001
2158
  scan.footHi = armHi;
2002
2159
  }
2003
2160
  const JSON_FIELD_ANCHOR = '"';
2004
- function jsonSecretFieldValueQuoteAt(text, p) {
2161
+ const JSON_FIELD_BACK = Math.max(...JSON_FIELD_QUOTE_FORMS.map((f) => f.length));
2162
+ function jsonSecretFieldValueQuoteAt(text, p, depth) {
2163
+ const form = JSON_FIELD_QUOTE_FORMS[depth];
2005
2164
  const n = text.length;
2006
- let i = p + 1;
2165
+ const nameFrom = p + form.length;
2166
+ let i = nameFrom;
2007
2167
  for (; i < n; i++) {
2008
2168
  const c = text.charCodeAt(i);
2009
- if (c === CH_QUOTE)
2010
- break;
2011
- if (i - p > CREDENTIAL_NAME_MAX)
2012
- return -1;
2013
2169
  if (!(isAsciiAlnumCode(c) || c === CH_UNDERSCORE || c === CH_MINUS))
2170
+ break;
2171
+ if (i - nameFrom >= CREDENTIAL_NAME_MAX)
2014
2172
  return -1;
2015
2173
  }
2016
- if (i >= n || i === p + 1 || credentialNameAnchor(text.slice(p + 1, i)) === undefined)
2174
+ if (i === nameFrom || !text.startsWith(form, i) || credentialNameAnchor(text.slice(nameFrom, i)) === undefined)
2017
2175
  return -1;
2018
- i += 1;
2176
+ i += form.length;
2019
2177
  while (i < n && isRegexSpace(text.charCodeAt(i)))
2020
2178
  i += 1;
2021
2179
  if (text.charCodeAt(i) !== CH_COLON)
@@ -2023,38 +2181,99 @@ function jsonSecretFieldValueQuoteAt(text, p) {
2023
2181
  i += 1;
2024
2182
  while (i < n && isRegexSpace(text.charCodeAt(i)))
2025
2183
  i += 1;
2026
- return text.charCodeAt(i) === CH_QUOTE ? i : -1;
2184
+ return text.startsWith(form, i) ? i : -1;
2027
2185
  }
2028
2186
  const STRING_OPEN = -2;
2029
- function jsonStringEndAt(text, q, isHead) {
2030
- const n = text.length;
2031
- for (let i = q + 1; i < n;) {
2187
+ const U_END = 0;
2188
+ const U_QUOTE = 1;
2189
+ const U_BSL = 2;
2190
+ const U_ORD = 3;
2191
+ const U_LT = 4;
2192
+ const U_BREAK = 5;
2193
+ const packUnit8 = (kind, next) => next * 8 + kind;
2194
+ const unit8Kind = (u) => (u % 8);
2195
+ const unit8Next = (u) => (u - (u % 8)) / 8;
2196
+ function escapedUnitAt(text, i, depth) {
2197
+ if (i >= text.length)
2198
+ return packUnit8(U_END, i);
2199
+ if (depth === 0) {
2032
2200
  const c = text.charCodeAt(i);
2033
- if (c === CH_QUOTE)
2034
- return i + 1;
2035
- if (c === CH_BACKSLASH) {
2036
- if (i + 1 < n && !isLineTerminator(text.charCodeAt(i + 1))) {
2037
- i += 2;
2201
+ return packUnit8(c === CH_QUOTE ? U_QUOTE : c === CH_BACKSLASH ? U_BSL : isLineTerminator(c) ? U_LT : U_ORD, i + 1);
2202
+ }
2203
+ const u = escapedUnitAt(text, i, depth - 1);
2204
+ switch (unit8Kind(u)) {
2205
+ case U_QUOTE: return packUnit8(U_BREAK, unit8Next(u));
2206
+ case U_BSL: return escapePairUnit(text, unit8Next(u), depth - 1);
2207
+ case U_ORD:
2208
+ case U_LT:
2209
+ case U_BREAK:
2210
+ case U_END: return u;
2211
+ }
2212
+ }
2213
+ function escapePairUnit(text, i, depth) {
2214
+ const v = escapedUnitAt(text, i, depth);
2215
+ const next = unit8Next(v);
2216
+ switch (unit8Kind(v)) {
2217
+ case U_BSL: return packUnit8(U_BSL, next);
2218
+ case U_QUOTE: return packUnit8(U_QUOTE, next);
2219
+ case U_ORD: return packUnit8(U_ORD, next);
2220
+ case U_LT:
2221
+ case U_BREAK: return packUnit8(U_BREAK, next);
2222
+ case U_END: return packUnit8(U_END, next);
2223
+ }
2224
+ }
2225
+ function jsonStringEndAt(text, from, depth, isHead) {
2226
+ for (let i = from;;) {
2227
+ const u = escapedUnitAt(text, i, depth);
2228
+ switch (unit8Kind(u)) {
2229
+ case U_QUOTE:
2230
+ return unit8Next(u);
2231
+ case U_ORD:
2232
+ case U_LT:
2233
+ i = unit8Next(u);
2234
+ continue;
2235
+ case U_BSL: {
2236
+ const v = escapedUnitAt(text, unit8Next(u), depth);
2237
+ const vk = unit8Kind(v);
2238
+ if (vk === U_END)
2239
+ return isHead ? STRING_OPEN : -1;
2240
+ if (vk === U_LT || vk === U_BREAK)
2241
+ return -1;
2242
+ i = unit8Next(v);
2038
2243
  continue;
2039
2244
  }
2040
- return isHead && i + 1 >= n ? STRING_OPEN : -1;
2245
+ case U_BREAK:
2246
+ return -1;
2247
+ case U_END:
2248
+ return isHead ? STRING_OPEN : -1;
2041
2249
  }
2042
- i += 1;
2043
2250
  }
2044
- return isHead ? STRING_OPEN : -1;
2045
2251
  }
2046
2252
  function collectJsonSecretFieldSpans(text, span, isHead) {
2253
+ let from = 0;
2047
2254
  for (let p = text.indexOf(JSON_FIELD_ANCHOR); p >= 0;) {
2048
- const q = jsonSecretFieldValueQuoteAt(text, p);
2049
- const found = q < 0 ? -1 : jsonStringEndAt(text, q, isHead);
2050
- if (found === -1) {
2051
- p = text.indexOf(JSON_FIELD_ANCHOR, p + 1);
2052
- continue;
2255
+ let run = 0;
2256
+ while (run < JSON_FIELD_BACK - 1 && p - run - 1 >= from && text.charCodeAt(p - run - 1) === CH_BACKSLASH)
2257
+ run += 1;
2258
+ let next = -1;
2259
+ for (let d = JSON_FIELD_QUOTE_FORMS.length - 1; d >= 0 && next < 0; d--) {
2260
+ const form = JSON_FIELD_QUOTE_FORMS[d];
2261
+ const start = p - (form.length - 1);
2262
+ if (start < p - run)
2263
+ continue;
2264
+ const q = jsonSecretFieldValueQuoteAt(text, start, d);
2265
+ const found = q < 0 ? -1 : jsonStringEndAt(text, q + form.length, d, isHead);
2266
+ if (found === -1)
2267
+ continue;
2268
+ const open = found === STRING_OPEN;
2269
+ const end = open ? text.length : found;
2270
+ span(q, end, open, JSON_FIELD_VALUE_REPLS[d]);
2271
+ from = scanResumeAfterHit(start, end, JSON_FIELD_BACK);
2272
+ next = text.indexOf(JSON_FIELD_ANCHOR, from);
2273
+ if (next < 0)
2274
+ return;
2053
2275
  }
2054
- const open = found === STRING_OPEN;
2055
- const end = open ? text.length : found;
2056
- span(q, end, open);
2057
- p = text.indexOf(JSON_FIELD_ANCHOR, scanResumeAfterHit(p, end, JSON_FIELD_ANCHOR.length));
2276
+ p = next >= 0 ? next : text.indexOf(JSON_FIELD_ANCHOR, p + 1);
2058
2277
  }
2059
2278
  }
2060
2279
  const STRONG_AUTH_SCHEMES = new Set(["basic", "bearer"]);
@@ -2443,7 +2662,7 @@ export function structuralScannerForTest(arm) {
2443
2662
  case "json-secret-field":
2444
2663
  return { declared: new RegExp(JSON_SECRET_FIELD_PATTERN[0].source, JSON_SECRET_FIELD_PATTERN[0].flags), spans: collect(collectJsonSecretFieldSpans) };
2445
2664
  case "pem-fence":
2446
- return { declared: new RegExp(PEM_FENCE_PATTERN[0].source, PEM_FENCE_PATTERN[0].flags), spans: collect((text, span, isHead) => collectPemFenceSpans(buildForensicScan(NO_CREDENTIAL_SPANS, 1), text, span, isHead)) };
2665
+ return { declared: new RegExp(PEM_FENCE_PATTERN[0].source, PEM_FENCE_PATTERN[0].flags), spans: collect((text, span, isHead) => collectPemFenceSpans({ ...buildForensicScan(NO_CREDENTIAL_SPANS, 1), others: NO_CREDENTIAL_SPANS }, text, span, isHead)) };
2447
2666
  }
2448
2667
  }
2449
2668
  let ppkScanBytes = 0;
@@ -2476,9 +2695,9 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2476
2695
  }
2477
2696
  return p;
2478
2697
  };
2479
- const isVersionedHead = (at) => {
2698
+ const versionedHeadEnd = (at) => {
2480
2699
  const p = versionEnd(at);
2481
- return p > at + HEAD.length && text.startsWith(": ", p);
2700
+ return p > at + HEAD.length && text.startsWith(": ", p) ? p + 2 : -1;
2482
2701
  };
2483
2702
  const isOpenHead = (at) => {
2484
2703
  const p = versionEnd(at);
@@ -2486,8 +2705,11 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2486
2705
  };
2487
2706
  const nextVersionedHead = (from) => {
2488
2707
  let p = find(HEAD, from);
2489
- while (p !== Number.POSITIVE_INFINITY && (inOpaqueUnit(scan, p) || !isVersionedHead(p)))
2490
- p = find(HEAD, p + HEAD.length);
2708
+ for (; p !== Number.POSITIVE_INFINITY; p = find(HEAD, p + HEAD.length)) {
2709
+ const lineEnd = versionedHeadEnd(p);
2710
+ if (lineEnd >= 0 && !anchorHeld(scan, p, lineEnd))
2711
+ break;
2712
+ }
2491
2713
  return p;
2492
2714
  };
2493
2715
  const hexRunEnd = (from) => {
@@ -2555,7 +2777,7 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2555
2777
  at = end >= text.length ? Number.POSITIVE_INFINITY : nextVersionedHead(end);
2556
2778
  }
2557
2779
  const last = isHead ? text.lastIndexOf(HEAD) : -1;
2558
- if (last >= 0 && !inOpaqueUnit(scan, last) && isOpenHead(last))
2780
+ if (last >= 0 && isOpenHead(last) && !anchorHeld(scan, last, text.length))
2559
2781
  span(last, text.length, true);
2560
2782
  }
2561
2783
  const armPool = (() => {
@@ -2611,7 +2833,7 @@ const armPool = (() => {
2611
2833
  order,
2612
2834
  phase: 1,
2613
2835
  lexical,
2614
- collect: (text, json, eat, out, isHead) => arm(text, json, eat, (start, end, undecided) => out.push(undecided === true ? { start, end, repl, rank, order, undecided } : { start, end, repl, rank, order }), isHead),
2836
+ collect: (text, json, eat, out, isHead) => arm(text, json, eat, (start, end, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided } : { start, end, repl: r, rank, order }), isHead),
2615
2837
  retry: undefined,
2616
2838
  headSrc: undefined,
2617
2839
  headFirst: undefined,
@@ -2629,7 +2851,7 @@ const armPool = (() => {
2629
2851
  order,
2630
2852
  phase: 2,
2631
2853
  lexical: false,
2632
- collect: (scan, text, json, eat, out, isHead) => arm(scan, text, json, eat, (start, end, undecided) => out.push(undecided === true ? { start, end, repl, rank, order, undecided } : { start, end, repl, rank, order }), isHead),
2854
+ collect: (scan, text, json, eat, out, isHead) => arm(scan, text, json, eat, (start, end, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided } : { start, end, repl: r, rank, order }), isHead),
2633
2855
  retry: undefined,
2634
2856
  headSrc: undefined,
2635
2857
  headFirst: undefined,
@@ -2641,7 +2863,7 @@ const armPool = (() => {
2641
2863
  if (row === PEM_FENCE_PATTERN)
2642
2864
  return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), "pem-armor", "other-families");
2643
2865
  if (row === SSH2_FENCE_PATTERN)
2644
- return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, span, isHead));
2866
+ return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, json, span, isHead), "ssh2-armor");
2645
2867
  if (row === URL_CREDENTIAL_PATTERN)
2646
2868
  return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead));
2647
2869
  if (row === JSON_SECRET_FIELD_PATTERN)
@@ -2651,9 +2873,9 @@ const armPool = (() => {
2651
2873
  const members = [];
2652
2874
  for (const row of STRUCTURAL_SECRET_PATTERNS)
2653
2875
  members.push(structuralScannerMember(row, members.length));
2654
- members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead)));
2655
- members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span) => collectPemFragmentSpans(scan, text, json, span), "pem-armor"));
2656
- members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead)));
2876
+ members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead), "putty-ppk"));
2877
+ members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectPemFragmentSpans(scan, text, json, span, isHead), "pem-armor"));
2878
+ members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead), "putty-ppk"));
2657
2879
  members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span, isHead) => collectAnchoredValueSpans(text, json, span, isHead), ARM_RANK.keyword, true));
2658
2880
  for (const row of KEYWORD_SECRET_PATTERNS)
2659
2881
  members.push(regexMember(row, ARM_RANK.keyword, members.length));
@@ -2683,8 +2905,15 @@ const armPool = (() => {
2683
2905
  }
2684
2906
  };
2685
2907
  const lanePhase2 = phase2.filter(inScope);
2686
- const viewKeys = [...new Set(lanePhase2.map(viewKeyOf))];
2687
- return { phase1: phase1.filter(inScope), phase2: lanePhase2, retry: retryMembers.filter(inScope), viewKeys, viewOf: lanePhase2.map((m) => viewKeys.indexOf(viewKeyOf(m))) };
2908
+ const viewKeys = [...new Set([...lanePhase2.map(viewKeyOf), ...lanePhase2.flatMap((m) => (m.family === undefined ? [] : [m.family]))])];
2909
+ return {
2910
+ phase1: phase1.filter(inScope),
2911
+ phase2: lanePhase2,
2912
+ retry: retryMembers.filter(inScope),
2913
+ viewKeys,
2914
+ viewOf: lanePhase2.map((m) => viewKeys.indexOf(viewKeyOf(m))),
2915
+ othersOf: lanePhase2.map((m) => (m.family === undefined ? -1 : viewKeys.indexOf(m.family))),
2916
+ };
2688
2917
  };
2689
2918
  const LANES = {
2690
2919
  "free-text": laneOf("free-text"),
@@ -2745,12 +2974,16 @@ const armPool = (() => {
2745
2974
  const before = spans.length;
2746
2975
  for (let j = 0; j < lane.phase2.length; j++) {
2747
2976
  const view = views[lane.viewOf[j]];
2977
+ const oi = lane.othersOf[j];
2978
+ const others = oi < 0 ? undefined : views[oi];
2748
2979
  const dirty = view.dirty;
2749
- if (dirty !== undefined && footprintClean(dirty, foot[2 * j], foot[2 * j + 1])) {
2980
+ const lo = foot[2 * j];
2981
+ const hi = foot[2 * j + 1];
2982
+ if (dirty !== undefined && footprintClean(dirty, lo, hi) && (others === undefined || (others.dirty !== undefined && footprintClean(others.dirty, lo, hi)))) {
2750
2983
  forensicSkips += 1;
2751
2984
  continue;
2752
2985
  }
2753
- useEvidenceView(scan, view.table, dirty);
2986
+ useEvidenceView(scan, view.table, dirty, others?.table);
2754
2987
  scan.footLo = FOOT_NONE;
2755
2988
  scan.footHi = -1;
2756
2989
  lane.phase2[j].collect(scan, text, json, level === 0 ? eat : undefined, spans, isHead);