@sema-agent/server 7.139.0 → 7.141.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 (328) hide show
  1. package/MIGRATION.md +471 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +94 -26
  5. package/dist/adoption/runner.js +1 -1
  6. package/dist/adoption/sql.js +1 -1
  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 +37 -22
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval.d.ts +15 -0
  12. package/dist/approval.js +10 -0
  13. package/dist/audit.d.ts +4 -2
  14. package/dist/audit.js +2 -2
  15. package/dist/bench/s1/live-deps.js +3 -2
  16. package/dist/bench/s1/row.d.ts +5 -2
  17. package/dist/bench/s1/row.js +2 -22
  18. package/dist/boot/adoption.js +1 -1
  19. package/dist/boot/device-lane.d.ts +11 -0
  20. package/dist/boot/device-lane.js +16 -0
  21. package/dist/boot/engine-lease.d.ts +24 -3
  22. package/dist/boot/engine-lease.js +9 -2
  23. package/dist/boot/execution-env.js +18 -12
  24. package/dist/boot/execution-lane.d.ts +8 -0
  25. package/dist/boot/execution-lane.js +6 -1
  26. package/dist/boot/memory-boundary.d.ts +16 -53
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +16 -12
  29. package/dist/boot/memory-consolidation.js +24 -13
  30. package/dist/boot/reapers.d.ts +2 -2
  31. package/dist/boot/resolve-spec.d.ts +3 -5
  32. package/dist/boot/resolve-spec.js +9 -8
  33. package/dist/boot/runner-deps.d.ts +3 -3
  34. package/dist/boot/runner-deps.js +1 -0
  35. package/dist/boot/session-faces.d.ts +1 -1
  36. package/dist/boot/session-faces.js +4 -3
  37. package/dist/boot/shutdown.js +5 -0
  38. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  39. package/dist/boot/stage-03-store-backend.js +2 -3
  40. package/dist/boot/stage-04-stores.d.ts +2 -6
  41. package/dist/boot/stage-04-stores.js +5 -5
  42. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  43. package/dist/boot/stage-05-execution-env.js +3 -1
  44. package/dist/boot/stage-06-runners.d.ts +2 -8
  45. package/dist/boot/stage-06-runners.js +3 -3
  46. package/dist/boot/stage-07-capability-layer.d.ts +2 -7
  47. package/dist/boot/stage-07-capability-layer.js +8 -10
  48. package/dist/boot/stage-08-reapers.d.ts +2 -7
  49. package/dist/boot/stage-09-leader.d.ts +2 -7
  50. package/dist/boot/stage-10-http-server.d.ts +2 -7
  51. package/dist/boot/stage-10-http-server.js +6 -4
  52. package/dist/boot/stores.d.ts +3 -6
  53. package/dist/boot/stores.js +37 -44
  54. package/dist/boot/task-list-lane.d.ts +1 -1
  55. package/dist/boot/task-list-lane.js +1 -1
  56. package/dist/boot/web-admin.js +36 -9
  57. package/dist/capabilities/execution-lane.d.ts +2 -2
  58. package/dist/capabilities/scenarios.d.ts +1 -1
  59. package/dist/capability-facts.d.ts +9 -0
  60. package/dist/capability-facts.js +9 -0
  61. package/dist/card-void.d.ts +38 -0
  62. package/dist/card-void.js +23 -0
  63. package/dist/config-catalog.js +12 -8
  64. package/dist/config-provider.d.ts +18 -17
  65. package/dist/config-provider.js +28 -36
  66. package/dist/config-types.d.ts +36 -9
  67. package/dist/config.js +56 -46
  68. package/dist/delegated-session-owner.d.ts +23 -20
  69. package/dist/delegated-session-owner.js +2 -2
  70. package/dist/deployment-shape.d.ts +9 -8
  71. package/dist/deployment-shape.js +4 -4
  72. package/dist/device-store.d.ts +7 -0
  73. package/dist/device-store.js +3 -0
  74. package/dist/execution-config-root.d.ts +22 -0
  75. package/dist/execution-config-root.js +35 -0
  76. package/dist/execution-lane-caps.d.ts +145 -107
  77. package/dist/execution-lane-caps.js +144 -83
  78. package/dist/execution-placement.d.ts +3 -0
  79. package/dist/execution-placement.js +7 -0
  80. package/dist/fleet/drain-entry.d.ts +3 -0
  81. package/dist/hosted-posture.d.ts +8 -0
  82. package/dist/hosted-posture.js +4 -0
  83. package/dist/http/admission.js +3 -3
  84. package/dist/http/caller-trust.d.ts +3 -2
  85. package/dist/http/caller-trust.js +2 -1
  86. package/dist/http/dispatch.js +4 -0
  87. package/dist/http/live-frame.d.ts +24 -0
  88. package/dist/http/live-frame.js +16 -0
  89. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  90. package/dist/http/memory-compliance-failure-reply.js +5 -0
  91. package/dist/http/memory-layer-reply.d.ts +30 -0
  92. package/dist/http/memory-layer-reply.js +24 -0
  93. package/dist/http/resume-legs.js +49 -35
  94. package/dist/http/retired-service-deps.d.ts +1 -1
  95. package/dist/http/retired-service-deps.js +1 -1
  96. package/dist/http/route-table.js +2 -0
  97. package/dist/http/routes/adoption.js +1 -1
  98. package/dist/http/routes/approvals-assistant.js +1 -0
  99. package/dist/http/routes/capabilities.js +5 -4
  100. package/dist/http/routes/diagnostics.d.ts +0 -19
  101. package/dist/http/routes/diagnostics.js +16 -2
  102. package/dist/http/routes/memory-bundle.d.ts +2 -2
  103. package/dist/http/routes/memory-bundle.js +4 -1
  104. package/dist/http/routes/memory-compliance.js +9 -2
  105. package/dist/http/routes/memory-consolidation.js +12 -1
  106. package/dist/http/routes/memory-origin.js +14 -2
  107. package/dist/http/routes/memory-policy.js +7 -2
  108. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  109. package/dist/http/routes/run-permission-mode.js +170 -0
  110. package/dist/http/routes/session-sync.d.ts +3 -6
  111. package/dist/http/routes/session-sync.js +7 -2
  112. package/dist/http/routes/sessions.js +33 -15
  113. package/dist/http/routes/tasks.js +67 -68
  114. package/dist/http/run-read-face.js +3 -3
  115. package/dist/http/send.d.ts +1 -1
  116. package/dist/http/send.js +2 -2
  117. package/dist/http/server.d.ts +14 -27
  118. package/dist/http/server.js +2 -2
  119. package/dist/http/sse-log.d.ts +3 -8
  120. package/dist/http/stored-body-replay.d.ts +3 -3
  121. package/dist/http/stored-body-replay.js +3 -1
  122. package/dist/identity-source.d.ts +1 -1
  123. package/dist/identity-source.js +1 -1
  124. package/dist/instruction-source-notice.d.ts +7 -0
  125. package/dist/instruction-source-notice.js +6 -6
  126. package/dist/lane-home-probe.d.ts +68 -42
  127. package/dist/lane-home-probe.js +53 -30
  128. package/dist/leader/wire.js +5 -4
  129. package/dist/mcp-assertion-header.d.ts +4 -0
  130. package/dist/mcp-assertion-header.js +5 -0
  131. package/dist/memory-bundle-engine.d.ts +16 -9
  132. package/dist/memory-bundle-engine.js +29 -6
  133. package/dist/memory-operator-faces.d.ts +85 -30
  134. package/dist/memory-operator-faces.js +136 -30
  135. package/dist/memory-posture.d.ts +1 -1
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +33 -9
  141. package/dist/memory-scope.js +17 -5
  142. package/dist/memory-sync-client.d.ts +34 -9
  143. package/dist/memory-sync-client.js +108 -28
  144. package/dist/observability/fail-open.d.ts +30 -2
  145. package/dist/observability/fail-open.js +30 -2
  146. package/dist/observability/metrics.js +0 -1
  147. package/dist/observability/run-terminal.d.ts +6 -2
  148. package/dist/observability/run-terminal.js +25 -26
  149. package/dist/observability/secret-env-scrub.d.ts +20 -1
  150. package/dist/observability/secret-env-scrub.js +14 -0
  151. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  152. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  153. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  154. package/dist/orchestration/workflow-notify-journal.js +2 -1
  155. package/dist/permission-mode-admission.d.ts +68 -0
  156. package/dist/permission-mode-admission.js +23 -0
  157. package/dist/permission-rule-list.d.ts +55 -6
  158. package/dist/permission-rule-list.js +52 -5
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-sql.js +14 -14
  161. package/dist/plugins/approval-nonce-store.js +4 -4
  162. package/dist/plugins/blob-backend.d.ts +4 -4
  163. package/dist/plugins/blob-backend.js +7 -7
  164. package/dist/plugins/breaker-state-sql.js +2 -2
  165. package/dist/plugins/caching-session-store.d.ts +6 -0
  166. package/dist/plugins/caching-session-store.js +3 -0
  167. package/dist/plugins/checkpoint-index-columns.js +2 -2
  168. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  169. package/dist/plugins/checkpoint-store-sql.js +90 -19
  170. package/dist/plugins/device-store-sql.d.ts +5 -2
  171. package/dist/plugins/device-store-sql.js +23 -20
  172. package/dist/plugins/ensure-index.d.ts +1 -2
  173. package/dist/plugins/execution-placement-sql.d.ts +19 -9
  174. package/dist/plugins/execution-placement-sql.js +20 -9
  175. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  176. package/dist/plugins/file-history-store-sql.js +4 -4
  177. package/dist/plugins/file-run-store.d.ts +2 -2
  178. package/dist/plugins/file-run-store.js +1 -1
  179. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  180. package/dist/plugins/fork-routing-session-store.js +2 -61
  181. package/dist/plugins/image-bake-store-sql.js +9 -9
  182. package/dist/plugins/image-index-sql.js +11 -11
  183. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  184. package/dist/plugins/k8s-exec-protocol.js +6 -1
  185. package/dist/plugins/leader-run-store-sql.js +4 -4
  186. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  187. package/dist/plugins/local-checkpoint-store.js +22 -0
  188. package/dist/plugins/local-session-store.d.ts +227 -35
  189. package/dist/plugins/local-session-store.js +518 -37
  190. package/dist/plugins/local-session-window.d.ts +17 -3
  191. package/dist/plugins/local-session-window.js +7 -13
  192. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  193. package/dist/plugins/mailbox-store-sql.js +4 -4
  194. package/dist/plugins/memory-engine-tidb.js +5 -5
  195. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  196. package/dist/plugins/memory-run-store.d.ts +2 -2
  197. package/dist/plugins/memory-run-store.js +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +52 -11
  200. package/dist/plugins/one-time-migrations.js +101 -39
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.js +2 -2
  203. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  204. package/dist/plugins/permission-rule-store-sql.js +16 -16
  205. package/dist/plugins/pg-pool.js +3 -2
  206. package/dist/plugins/pg-session-storage.d.ts +2 -0
  207. package/dist/plugins/pg-session-storage.js +3 -2
  208. package/dist/plugins/remote-env-adb.d.ts +7 -24
  209. package/dist/plugins/remote-env-adb.js +22 -5
  210. package/dist/plugins/remote-env-device.d.ts +2 -28
  211. package/dist/plugins/remote-env-device.js +2 -1
  212. package/dist/plugins/remote-env-e2b.d.ts +40 -27
  213. package/dist/plugins/remote-env-e2b.js +180 -20
  214. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  215. package/dist/plugins/remote-env-file-error.js +8 -2
  216. package/dist/plugins/remote-env-host.d.ts +19 -35
  217. package/dist/plugins/remote-env-host.js +26 -3
  218. package/dist/plugins/remote-env-k8s.d.ts +22 -14
  219. package/dist/plugins/remote-env-k8s.js +150 -26
  220. package/dist/plugins/remote-env-local-docker.d.ts +27 -24
  221. package/dist/plugins/remote-env-local-docker.js +91 -24
  222. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  223. package/dist/plugins/remote-env-ssh.js +4 -3
  224. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  225. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  226. package/dist/plugins/retention-lane-store-sql.js +2 -2
  227. package/dist/plugins/retention-store-sql.d.ts +1 -1
  228. package/dist/plugins/retention-store-sql.js +6 -6
  229. package/dist/plugins/run-store-sql.d.ts +3 -3
  230. package/dist/plugins/run-store-sql.js +19 -18
  231. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  232. package/dist/plugins/sandbox-read-floor.js +608 -0
  233. package/dist/plugins/schema-consistency.d.ts +6 -6
  234. package/dist/plugins/schema-consistency.js +1 -1
  235. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  236. package/dist/plugins/schema-ensure-legs.js +2 -2
  237. package/dist/plugins/schema-live-columns.d.ts +1 -1
  238. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  239. package/dist/plugins/session-ownership.d.ts +2 -1
  240. package/dist/plugins/session-placement.d.ts +17 -4
  241. package/dist/plugins/session-placement.js +2 -1
  242. package/dist/plugins/session-rules-column.d.ts +1 -1
  243. package/dist/plugins/session-rules-column.js +1 -1
  244. package/dist/plugins/session-store.d.ts +18 -20
  245. package/dist/plugins/session-store.js +4 -12
  246. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  247. package/dist/plugins/session-workspace-cell.js +1 -1
  248. package/dist/plugins/shared-memory-store-sql.js +2 -2
  249. package/dist/plugins/sql-driver.d.ts +10 -5
  250. package/dist/plugins/sql-driver.js +6 -6
  251. package/dist/plugins/sql-errors.js +1 -1
  252. package/dist/plugins/store-backend.d.ts +35 -6
  253. package/dist/plugins/store-backend.js +27 -10
  254. package/dist/plugins/store-contracts.d.ts +9 -2
  255. package/dist/plugins/task-list-store-sql.js +11 -11
  256. package/dist/plugins/tidb-pool.js +7 -6
  257. package/dist/plugins/tidb-session-storage.js +4 -4
  258. package/dist/plugins/tidb-session-store.d.ts +4 -1
  259. package/dist/plugins/tidb-session-store.js +16 -15
  260. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  261. package/dist/plugins/tool-result-store-sql.js +3 -3
  262. package/dist/plugins/user-input-ledger-sql.js +2 -2
  263. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  264. package/dist/plugins/workflow-run-store-sql.js +19 -19
  265. package/dist/plugins/write-behind-counter.d.ts +2 -3
  266. package/dist/plugins/write-behind-counter.js +5 -5
  267. package/dist/resource-suspend.d.ts +14 -12
  268. package/dist/resource-suspend.js +1 -1
  269. package/dist/resource-window.d.ts +4 -3
  270. package/dist/run-cancel-context.d.ts +34 -0
  271. package/dist/run-cancel-context.js +7 -0
  272. package/dist/run-local.js +14 -19
  273. package/dist/runs.d.ts +47 -16
  274. package/dist/runs.js +112 -113
  275. package/dist/security.d.ts +2 -2
  276. package/dist/server-secret-env.d.ts +2 -1
  277. package/dist/server-secret-env.js +2 -4
  278. package/dist/session-error-wire.d.ts +22 -4
  279. package/dist/session-error-wire.js +16 -0
  280. package/dist/session-incarnation.d.ts +10 -4
  281. package/dist/session-incarnation.js +1 -1
  282. package/dist/session-policy-wire.d.ts +1 -1
  283. package/dist/session-sync-kernel.d.ts +42 -1
  284. package/dist/session-sync-kernel.js +17 -0
  285. package/dist/session-sync.js +16 -4
  286. package/dist/sql-engine-coordinates.d.ts +49 -0
  287. package/dist/sql-engine-coordinates.js +27 -0
  288. package/dist/sql-engine-posture.d.ts +1 -1
  289. package/dist/sql-engine-posture.js +1 -1
  290. package/dist/standing-answer-grant.d.ts +31 -2
  291. package/dist/standing-answer-grant.js +14 -2
  292. package/dist/store-posture.d.ts +1 -1
  293. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  294. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  295. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  296. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  297. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  298. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  299. package/dist/task-a2a.d.ts +1 -1
  300. package/dist/task-a2a.js +5 -6
  301. package/dist/task-mcp.d.ts +21 -8
  302. package/dist/task-mcp.js +17 -9
  303. package/dist/task-settings.d.ts +52 -25
  304. package/dist/task-settings.js +91 -28
  305. package/dist/terminal.d.ts +103 -19
  306. package/dist/terminal.js +72 -20
  307. package/dist/trace/core-keyset-guard.d.ts +79 -2
  308. package/dist/trace/exit-seat.d.ts +7 -0
  309. package/dist/trace/exit-seat.js +1 -0
  310. package/dist/trace/model-segment.d.ts +2 -2
  311. package/dist/trace/redact.d.ts +3 -1
  312. package/dist/trace/redact.js +395 -110
  313. package/dist/trace/self-minted-wire.d.ts +226 -2
  314. package/dist/trace/self-minted-wire.js +79 -0
  315. package/dist/verifier-pause-card.d.ts +8 -0
  316. package/dist/verifier-pause-card.js +19 -0
  317. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  318. package/dist/web-admin-client/apply-effective.js +2 -2
  319. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  320. package/dist/web-admin-client/candidate-gate.js +1 -1
  321. package/dist/web-admin-client/http-client.d.ts +5 -4
  322. package/dist/web-admin-client/http-client.js +2 -2
  323. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  324. package/dist/web-admin-client/read-warnings.js +5 -9
  325. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  326. package/package.json +2 -2
  327. package/wire-changes/server@7.140.0.json +184 -0
  328. package/wire-changes/server@7.141.0.json +367 -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,38 +1190,54 @@ 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 st = scan.table.starts;
1172
- if (st.length === 0)
1173
- return false;
1174
- const k = firstCredentialSpanFrom(scan.table, from);
1175
- if (k >= st.length || st[k] >= to)
1176
- return false;
1177
- let n = 0;
1178
- for (let p = from; p < to;) {
1179
- if (n > max)
1180
- return false;
1181
- const unit = opaqueUnitEnd(scan, p);
1182
- p = unit > 0 && unit <= to ? unit : p + 1;
1183
- n += 1;
1209
+ const lo = firstCredentialSpanFrom(t, from);
1210
+ let a = lo;
1211
+ let b = t.ends.length;
1212
+ while (a < b) {
1213
+ const mid = (a + b) >>> 1;
1214
+ if (t.ends[mid] <= to)
1215
+ a = mid + 1;
1216
+ else
1217
+ b = mid;
1184
1218
  }
1185
- return n <= max;
1186
- }
1187
- function collectSsh2FenceSpans(scan, text, span, isHead) {
1219
+ if (a === lo)
1220
+ return false;
1221
+ const saved = unitSavings(t);
1222
+ return to - from - (saved[a] - saved[lo]) <= max;
1223
+ }
1224
+ const UNIT_SAVINGS = new WeakMap();
1225
+ function unitSavings(t) {
1226
+ let saved = UNIT_SAVINGS.get(t);
1227
+ if (saved === undefined) {
1228
+ const n = t.starts.length;
1229
+ saved = new Float64Array(n + 1);
1230
+ for (let k = 0; k < n; k++)
1231
+ saved[k + 1] = saved[k] + (t.ends[k] - t.starts[k] - 1);
1232
+ UNIT_SAVINGS.set(t, saved);
1233
+ }
1234
+ return saved;
1235
+ }
1236
+ export function unitCountWithinForTest(table, from, to, max) {
1237
+ const t = { starts: Int32Array.from(table, (x) => x[0]), ends: Int32Array.from(table, (x) => x[1]) };
1238
+ return unitCountWithin(buildForensicScan(t, 1), from, to, max);
1239
+ }
1240
+ function collectSsh2FenceSpans(scan, text, json, span, isHead) {
1188
1241
  let cachedFrom = 0;
1189
1242
  let cachedAt;
1190
1243
  const nextClose = (from) => {
@@ -1201,29 +1254,78 @@ function collectSsh2FenceSpans(scan, text, span, isHead) {
1201
1254
  cachedAt = p;
1202
1255
  return p;
1203
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
+ };
1204
1290
  for (let from = 0;;) {
1205
1291
  const fence = text.indexOf(SSH2_FENCE_OPEN, from);
1206
1292
  if (fence < 0)
1207
1293
  return;
1208
- if (inOpaqueUnit(scan, fence)) {
1294
+ const bodyFrom = fence + SSH2_FENCE_OPEN.length;
1295
+ if (anchorHeld(scan, fence, bodyFrom)) {
1209
1296
  from = fence + 1;
1210
1297
  continue;
1211
1298
  }
1212
- const bodyFrom = fence + SSH2_FENCE_OPEN.length;
1213
1299
  if (isHead && unitCountWithin(scan, bodyFrom, text.length, SSH2_BODY_MAX)) {
1214
1300
  span(fence, text.length, true);
1215
1301
  return;
1216
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
+ }
1217
1315
  const close = nextClose(bodyFrom);
1218
- if (close < 0)
1219
- return;
1316
+ if (close < 0) {
1317
+ if (isHead)
1318
+ return;
1319
+ from = resume;
1320
+ continue;
1321
+ }
1220
1322
  if (!unitCountWithin(scan, bodyFrom, close, SSH2_BODY_MAX)) {
1221
- from = fence + 1;
1323
+ from = resume;
1222
1324
  continue;
1223
1325
  }
1224
1326
  const end = close + SSH2_FENCE_CLOSE.length;
1225
1327
  span(fence, end);
1226
- from = scanResumeAfterHit(fence, end, SSH2_FENCE_OPEN.length);
1328
+ from = Math.max(resume, scanResumeAfterHit(fence, end, SSH2_FENCE_OPEN.length));
1227
1329
  }
1228
1330
  }
1229
1331
  const PEM_END_OPEN = "-----END";
@@ -1241,16 +1343,20 @@ function pemFenceLineEnd(text, labelFrom) {
1241
1343
  }
1242
1344
  return isPemFenceLabel(text, labelFrom, close) ? close + PEM_FENCE_CLOSE.length : PEM_LINE_INVALID;
1243
1345
  }
1244
- function collectPemFenceSpans(text, span, isHead) {
1346
+ function collectPemFenceSpans(scan, text, span, isHead) {
1245
1347
  let endFrom = -1;
1246
1348
  let endAt = -1;
1247
1349
  let endAfter = -1;
1350
+ const visibleEndLine = (b) => {
1351
+ const after = pemFenceLineEnd(text, b + PEM_END_OPEN.length);
1352
+ return after >= 0 && !inOpaqueUnit(scan, b) ? after : -1;
1353
+ };
1248
1354
  const earliestEndFence = (from) => {
1249
1355
  if (endFrom >= 0 && from >= endFrom && (endAt < 0 || endAt >= from))
1250
1356
  return;
1251
1357
  let b = text.indexOf(PEM_END_OPEN, from);
1252
1358
  let after = -1;
1253
- while (b >= 0 && (after = pemFenceLineEnd(text, b + PEM_END_OPEN.length)) < 0)
1359
+ while (b >= 0 && (after = visibleEndLine(b)) < 0)
1254
1360
  b = text.indexOf(PEM_END_OPEN, b + 1);
1255
1361
  endFrom = from;
1256
1362
  endAt = b;
@@ -1265,6 +1371,10 @@ function collectPemFenceSpans(text, span, isHead) {
1265
1371
  from = fence + 1;
1266
1372
  continue;
1267
1373
  }
1374
+ if (anchorHeld(scan, fence, bodyFrom === PEM_LINE_OPEN ? text.length : bodyFrom)) {
1375
+ from = fence + 1;
1376
+ continue;
1377
+ }
1268
1378
  if (bodyFrom !== PEM_LINE_OPEN)
1269
1379
  earliestEndFence(bodyFrom);
1270
1380
  if (bodyFrom === PEM_LINE_OPEN || endAt < 0) {
@@ -1272,7 +1382,7 @@ function collectPemFenceSpans(text, span, isHead) {
1272
1382
  span(fence, text.length, true);
1273
1383
  return;
1274
1384
  }
1275
- if (endAt - bodyFrom > PEM_FENCE_BODY_MAX) {
1385
+ if (!unitCountWithin(scan, bodyFrom, endAt, PEM_FENCE_BODY_MAX)) {
1276
1386
  from = fence + 1;
1277
1387
  continue;
1278
1388
  }
@@ -1280,35 +1390,80 @@ function collectPemFenceSpans(text, span, isHead) {
1280
1390
  from = scanResumeAfterHit(fence, endAfter, PEM_FENCE_OPEN.length);
1281
1391
  }
1282
1392
  }
1283
- 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) {
1284
1444
  let cur = findPemFence(scan, text, 0);
1285
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);
1286
1459
  while (cur !== undefined) {
1287
1460
  const next = findPemFence(scan, text, cur.after);
1288
1461
  const limit = next === undefined ? text.length : next.fence;
1289
- let pos = cur.after;
1290
- let allowBare = inherited;
1291
- let hitLimit = false;
1292
- let rightmost = -1;
1293
- while (parsePemHeaderLine(scan, text, pos, limit, allowBare, json)) {
1294
- const e = rightmostPemRunEnd(scan, text, scan.hdr.search, scan.hdr.contentEnd);
1295
- if (e > rightmost)
1296
- rightmost = e;
1297
- pos = scan.hdr.next;
1298
- allowBare = false;
1299
- if (scan.hdr.hitLimit) {
1300
- hitLimit = true;
1301
- break;
1302
- }
1303
- if (scan.hdr.jsonEnd)
1304
- break;
1305
- }
1306
- inherited = hitLimit;
1307
- let bodyEnd = parsePemBody(scan, text, pos, limit);
1308
- if (bodyEnd < 0)
1309
- bodyEnd = rightmost;
1310
- if (bodyEnd >= 0)
1311
- 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);
1312
1467
  cur = next;
1313
1468
  }
1314
1469
  }
@@ -1414,10 +1569,6 @@ function collectHeadlessPpkSpans(scan, text, span, isHead) {
1414
1569
  const at = text.indexOf(KEY, search);
1415
1570
  if (at === -1)
1416
1571
  break;
1417
- if (inOpaqueUnit(scan, at)) {
1418
- search = at + KEY.length;
1419
- continue;
1420
- }
1421
1572
  let p = at + KEY.length;
1422
1573
  const d0 = p;
1423
1574
  while (p < n && opaqueUnitEnd(scan, p) < 0 && text.charCodeAt(p) >= 48 && text.charCodeAt(p) <= 57)
@@ -1426,6 +1577,10 @@ function collectHeadlessPpkSpans(scan, text, span, isHead) {
1426
1577
  search = at + KEY.length;
1427
1578
  continue;
1428
1579
  }
1580
+ if (anchorHeld(scan, at, p)) {
1581
+ search = at + KEY.length;
1582
+ continue;
1583
+ }
1429
1584
  let end = -1;
1430
1585
  let open = false;
1431
1586
  for (let from = p;;) {
@@ -1524,6 +1679,14 @@ const JSON_SECRET_FIELD_PATTERN = (() => {
1524
1679
  throw new Error(`redact: expected exactly one JSON secret-field rule (replacement '$1"«redacted»"') in FULL_ARM_PATTERNS, found ${rows.length}`);
1525
1680
  return rows[0];
1526
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
+ })();
1527
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;
1528
1691
  const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
1529
1692
  const isControlCode = (c) => c < 0x20 || c === 0x7f;
@@ -1610,7 +1773,7 @@ let forensicRescanMode = "incremental";
1610
1773
  export function setForensicRescanModeForTest(mode) {
1611
1774
  forensicRescanMode = mode;
1612
1775
  }
1613
- function foldCredentialSpans(prev, spans, from) {
1776
+ function foldCredentialSpans(prev, spans, from, admit) {
1614
1777
  const cap = spans.length - from;
1615
1778
  if (cap === 0)
1616
1779
  return { next: prev, dirty: NO_CREDENTIAL_SPANS };
@@ -1619,6 +1782,8 @@ function foldCredentialSpans(prev, spans, from) {
1619
1782
  let m = 0;
1620
1783
  for (let i = from; i < spans.length; i++) {
1621
1784
  const sp = spans[i];
1785
+ if (admit !== undefined && !admit(sp))
1786
+ continue;
1622
1787
  const k = firstCredentialSpanFrom(prev, sp.start + 1) - 1;
1623
1788
  if (k >= 0 && prev.ends[k] >= sp.end)
1624
1789
  continue;
@@ -1729,6 +1894,7 @@ const FOOT_NONE = 0x7fffffff;
1729
1894
  function buildForensicScan(table, armCount) {
1730
1895
  return {
1731
1896
  table,
1897
+ others: undefined,
1732
1898
  cursor: 0,
1733
1899
  dirty: undefined,
1734
1900
  footLo: FOOT_NONE,
@@ -1742,8 +1908,9 @@ function buildForensicScan(table, armCount) {
1742
1908
  hdr: { search: 0, contentEnd: 0, next: 0, hitLimit: false, jsonEnd: false },
1743
1909
  };
1744
1910
  }
1745
- function advanceEvidence(scan, table, dirty) {
1911
+ function useEvidenceView(scan, table, dirty, others) {
1746
1912
  scan.table = table;
1913
+ scan.others = others;
1747
1914
  scan.cursor = 0;
1748
1915
  scan.dirty = dirty;
1749
1916
  }
@@ -1777,6 +1944,22 @@ function inOpaqueUnit(scan, p) {
1777
1944
  const k = firstCredentialSpanFrom(t, p) - 1;
1778
1945
  return k >= 0 && st[k] < p && t.ends[k] > p;
1779
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
+ }
1780
1963
  function opaqueUnitEnd(scan, p) {
1781
1964
  noteEvidenceRead(scan, p);
1782
1965
  return opaqueUnitEndAt(scan, p);
@@ -1975,21 +2158,22 @@ function collectUrlCredentialSpans(scan, text, json, span, isHead) {
1975
2158
  scan.footHi = armHi;
1976
2159
  }
1977
2160
  const JSON_FIELD_ANCHOR = '"';
1978
- 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];
1979
2164
  const n = text.length;
1980
- let i = p + 1;
2165
+ const nameFrom = p + form.length;
2166
+ let i = nameFrom;
1981
2167
  for (; i < n; i++) {
1982
2168
  const c = text.charCodeAt(i);
1983
- if (c === CH_QUOTE)
1984
- break;
1985
- if (i - p > CREDENTIAL_NAME_MAX)
1986
- return -1;
1987
2169
  if (!(isAsciiAlnumCode(c) || c === CH_UNDERSCORE || c === CH_MINUS))
2170
+ break;
2171
+ if (i - nameFrom >= CREDENTIAL_NAME_MAX)
1988
2172
  return -1;
1989
2173
  }
1990
- 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)
1991
2175
  return -1;
1992
- i += 1;
2176
+ i += form.length;
1993
2177
  while (i < n && isRegexSpace(text.charCodeAt(i)))
1994
2178
  i += 1;
1995
2179
  if (text.charCodeAt(i) !== CH_COLON)
@@ -1997,38 +2181,99 @@ function jsonSecretFieldValueQuoteAt(text, p) {
1997
2181
  i += 1;
1998
2182
  while (i < n && isRegexSpace(text.charCodeAt(i)))
1999
2183
  i += 1;
2000
- return text.charCodeAt(i) === CH_QUOTE ? i : -1;
2184
+ return text.startsWith(form, i) ? i : -1;
2001
2185
  }
2002
2186
  const STRING_OPEN = -2;
2003
- function jsonStringEndAt(text, q, isHead) {
2004
- const n = text.length;
2005
- 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) {
2006
2200
  const c = text.charCodeAt(i);
2007
- if (c === CH_QUOTE)
2008
- return i + 1;
2009
- if (c === CH_BACKSLASH) {
2010
- if (i + 1 < n && !isLineTerminator(text.charCodeAt(i + 1))) {
2011
- 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);
2012
2243
  continue;
2013
2244
  }
2014
- 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;
2015
2249
  }
2016
- i += 1;
2017
2250
  }
2018
- return isHead ? STRING_OPEN : -1;
2019
2251
  }
2020
2252
  function collectJsonSecretFieldSpans(text, span, isHead) {
2253
+ let from = 0;
2021
2254
  for (let p = text.indexOf(JSON_FIELD_ANCHOR); p >= 0;) {
2022
- const q = jsonSecretFieldValueQuoteAt(text, p);
2023
- const found = q < 0 ? -1 : jsonStringEndAt(text, q, isHead);
2024
- if (found === -1) {
2025
- p = text.indexOf(JSON_FIELD_ANCHOR, p + 1);
2026
- 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;
2027
2275
  }
2028
- const open = found === STRING_OPEN;
2029
- const end = open ? text.length : found;
2030
- span(q, end, open);
2031
- 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);
2032
2277
  }
2033
2278
  }
2034
2279
  const STRONG_AUTH_SCHEMES = new Set(["basic", "bearer"]);
@@ -2417,7 +2662,7 @@ export function structuralScannerForTest(arm) {
2417
2662
  case "json-secret-field":
2418
2663
  return { declared: new RegExp(JSON_SECRET_FIELD_PATTERN[0].source, JSON_SECRET_FIELD_PATTERN[0].flags), spans: collect(collectJsonSecretFieldSpans) };
2419
2664
  case "pem-fence":
2420
- return { declared: new RegExp(PEM_FENCE_PATTERN[0].source, PEM_FENCE_PATTERN[0].flags), spans: collect(collectPemFenceSpans) };
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)) };
2421
2666
  }
2422
2667
  }
2423
2668
  let ppkScanBytes = 0;
@@ -2450,9 +2695,9 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2450
2695
  }
2451
2696
  return p;
2452
2697
  };
2453
- const isVersionedHead = (at) => {
2698
+ const versionedHeadEnd = (at) => {
2454
2699
  const p = versionEnd(at);
2455
- return p > at + HEAD.length && text.startsWith(": ", p);
2700
+ return p > at + HEAD.length && text.startsWith(": ", p) ? p + 2 : -1;
2456
2701
  };
2457
2702
  const isOpenHead = (at) => {
2458
2703
  const p = versionEnd(at);
@@ -2460,8 +2705,11 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2460
2705
  };
2461
2706
  const nextVersionedHead = (from) => {
2462
2707
  let p = find(HEAD, from);
2463
- while (p !== Number.POSITIVE_INFINITY && (inOpaqueUnit(scan, p) || !isVersionedHead(p)))
2464
- 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
+ }
2465
2713
  return p;
2466
2714
  };
2467
2715
  const hexRunEnd = (from) => {
@@ -2529,7 +2777,7 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2529
2777
  at = end >= text.length ? Number.POSITIVE_INFINITY : nextVersionedHead(end);
2530
2778
  }
2531
2779
  const last = isHead ? text.lastIndexOf(HEAD) : -1;
2532
- if (last >= 0 && !inOpaqueUnit(scan, last) && isOpenHead(last))
2780
+ if (last >= 0 && isOpenHead(last) && !anchorHeld(scan, last, text.length))
2533
2781
  span(last, text.length, true);
2534
2782
  }
2535
2783
  const armPool = (() => {
@@ -2574,6 +2822,7 @@ const armPool = (() => {
2574
2822
  retry: boundary === undefined ? undefined : (text, at, out) => collectStickySpans(boundary, text, at, out),
2575
2823
  headSrc: boundary === undefined ? undefined : headSrc,
2576
2824
  headFirst: boundary === undefined ? undefined : headFirst,
2825
+ family: undefined,
2577
2826
  };
2578
2827
  };
2579
2828
  const scannerMember = (token, order, arm, rank = ARM_RANK.structural, lexical = false) => {
@@ -2584,13 +2833,16 @@ const armPool = (() => {
2584
2833
  order,
2585
2834
  phase: 1,
2586
2835
  lexical,
2587
- 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),
2588
2837
  retry: undefined,
2589
2838
  headSrc: undefined,
2590
2839
  headFirst: undefined,
2840
+ family: undefined,
2591
2841
  };
2592
2842
  };
2593
- const forensicMember = (token, order, arm) => {
2843
+ const forensicMember = (token, order, arm, family = undefined, view = "all") => {
2844
+ if (view === "other-families" && family === undefined)
2845
+ throw new Error(`redact: forensic arm ${token} reads the other-families evidence view but declares no credential family`);
2594
2846
  const repl = spanRepl(token);
2595
2847
  const rank = ARM_RANK.structural;
2596
2848
  return {
@@ -2599,17 +2851,19 @@ const armPool = (() => {
2599
2851
  order,
2600
2852
  phase: 2,
2601
2853
  lexical: false,
2602
- 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),
2603
2855
  retry: undefined,
2604
2856
  headSrc: undefined,
2605
2857
  headFirst: undefined,
2858
+ family,
2859
+ view,
2606
2860
  };
2607
2861
  };
2608
2862
  const structuralScannerMember = (row, order) => {
2609
2863
  if (row === PEM_FENCE_PATTERN)
2610
- return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectPemFenceSpans(text, span, isHead));
2864
+ return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), "pem-armor", "other-families");
2611
2865
  if (row === SSH2_FENCE_PATTERN)
2612
- 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");
2613
2867
  if (row === URL_CREDENTIAL_PATTERN)
2614
2868
  return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead));
2615
2869
  if (row === JSON_SECRET_FIELD_PATTERN)
@@ -2619,9 +2873,9 @@ const armPool = (() => {
2619
2873
  const members = [];
2620
2874
  for (const row of STRUCTURAL_SECRET_PATTERNS)
2621
2875
  members.push(structuralScannerMember(row, members.length));
2622
- members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead)));
2623
- members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span) => collectPemFragmentSpans(scan, text, json, span)));
2624
- 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"));
2625
2879
  members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span, isHead) => collectAnchoredValueSpans(text, json, span, isHead), ARM_RANK.keyword, true));
2626
2880
  for (const row of KEYWORD_SECRET_PATTERNS)
2627
2881
  members.push(regexMember(row, ARM_RANK.keyword, members.length));
@@ -2636,6 +2890,13 @@ const armPool = (() => {
2636
2890
  const retryMembers = members.filter((m) => m.retry !== undefined);
2637
2891
  const phase1 = members.filter((m) => m.phase === 1);
2638
2892
  const phase2 = members.filter((m) => m.phase === 2);
2893
+ const familyOfOrder = members.map((m) => m.family);
2894
+ const viewKeyOf = (m) => {
2895
+ switch (m.view) {
2896
+ case "all": return undefined;
2897
+ case "other-families": return m.family;
2898
+ }
2899
+ };
2639
2900
  const laneOf = (scope) => {
2640
2901
  const inScope = (m) => {
2641
2902
  switch (scope) {
@@ -2643,7 +2904,16 @@ const armPool = (() => {
2643
2904
  case "bounded-unit": return !m.lexical;
2644
2905
  }
2645
2906
  };
2646
- return { phase1: phase1.filter(inScope), phase2: phase2.filter(inScope), retry: retryMembers.filter(inScope) };
2907
+ const lanePhase2 = phase2.filter(inScope);
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
+ };
2647
2917
  };
2648
2918
  const LANES = {
2649
2919
  "free-text": laneOf("free-text"),
@@ -2694,18 +2964,26 @@ const armPool = (() => {
2694
2964
  m.collect(text, json, eat, spans, isHead);
2695
2965
  closeCoveragePositions(0);
2696
2966
  if (lane.phase2.length > 0) {
2697
- const scan = buildForensicScan(credentialSpans(text), lane.phase2.length);
2967
+ const tokens = credentialSpans(text);
2968
+ const scan = buildForensicScan(tokens, lane.phase2.length);
2698
2969
  const reuse = forensicRescanMode === "incremental";
2699
2970
  const foot = scan.armFoot;
2971
+ const views = lane.viewKeys.map((key) => ({ key, table: tokens, dirty: undefined }));
2700
2972
  let folded = 0;
2701
2973
  for (let level = 0;; level += 1) {
2702
2974
  const before = spans.length;
2703
2975
  for (let j = 0; j < lane.phase2.length; j++) {
2704
- const dirty = scan.dirty;
2705
- if (dirty !== undefined && footprintClean(dirty, foot[2 * j], foot[2 * j + 1])) {
2976
+ const view = views[lane.viewOf[j]];
2977
+ const oi = lane.othersOf[j];
2978
+ const others = oi < 0 ? undefined : views[oi];
2979
+ const dirty = view.dirty;
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)))) {
2706
2983
  forensicSkips += 1;
2707
2984
  continue;
2708
2985
  }
2986
+ useEvidenceView(scan, view.table, dirty, others?.table);
2709
2987
  scan.footLo = FOOT_NONE;
2710
2988
  scan.footHi = -1;
2711
2989
  lane.phase2[j].collect(scan, text, json, level === 0 ? eat : undefined, spans, isHead);
@@ -2715,11 +2993,18 @@ const armPool = (() => {
2715
2993
  }
2716
2994
  closeCoveragePositions(before);
2717
2995
  forensicLevels += 1;
2718
- const grown = foldCredentialSpans(scan.table, spans, folded);
2996
+ let grew = false;
2997
+ for (const view of views) {
2998
+ const key = view.key;
2999
+ const grown = foldCredentialSpans(view.table, spans, folded, key === undefined ? undefined : (sp) => familyOfOrder[sp.order] !== key);
3000
+ if (grown.dirty.starts.length > 0)
3001
+ grew = true;
3002
+ view.table = grown.next;
3003
+ view.dirty = reuse ? grown.dirty : undefined;
3004
+ }
2719
3005
  folded = spans.length;
2720
- if (grown.dirty.starts.length === 0)
3006
+ if (!grew)
2721
3007
  break;
2722
- advanceEvidence(scan, grown.next, reuse ? grown.dirty : undefined);
2723
3008
  }
2724
3009
  }
2725
3010
  poolSpanCount += spans.length;