@sema-agent/server 7.135.0 → 7.137.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 (342) hide show
  1. package/MIGRATION.md +237 -2
  2. package/USAGE.md +146 -41
  3. package/deploy/sema-up/README.md +15 -1
  4. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  5. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +7 -1
  6. package/deploy/sema-up/chart/values.yaml +1 -1
  7. package/deploy/sema-up/stack/sema-stack-cluster.yaml +7 -1
  8. package/dist/approval-card.d.ts +60 -4
  9. package/dist/approval-card.js +18 -1
  10. package/dist/approval-content-kind.d.ts +7 -0
  11. package/dist/approval-content-kind.js +8 -1
  12. package/dist/approval-gate-currency.d.ts +48 -6
  13. package/dist/approval-gate-currency.js +16 -9
  14. package/dist/approval-hmac.d.ts +13 -1
  15. package/dist/approval-hmac.js +1 -1
  16. package/dist/approval.js +3 -3
  17. package/dist/auth-bridge.d.ts +34 -5
  18. package/dist/auth-bridge.js +22 -5
  19. package/dist/boot/budget-tracing.d.ts +1 -2
  20. package/dist/boot/budget-tracing.js +2 -2
  21. package/dist/boot/execution-env.d.ts +24 -1
  22. package/dist/boot/execution-env.js +69 -6
  23. package/dist/boot/org-memory.js +1 -1
  24. package/dist/boot/reapers.d.ts +3 -0
  25. package/dist/boot/reapers.js +15 -1
  26. package/dist/boot/resolve-spec.d.ts +4 -0
  27. package/dist/boot/resolve-spec.js +24 -22
  28. package/dist/boot/runner-deps.js +4 -2
  29. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  30. package/dist/boot/stage-04-stores.d.ts +2 -2
  31. package/dist/boot/stage-05-execution-env.d.ts +4 -2
  32. package/dist/boot/stage-05-execution-env.js +11 -2
  33. package/dist/boot/stage-06-runners.d.ts +4 -2
  34. package/dist/boot/stage-06-runners.js +5 -1
  35. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  36. package/dist/boot/stage-07-capability-layer.js +1 -1
  37. package/dist/boot/stage-08-reapers.d.ts +5 -2
  38. package/dist/boot/stage-08-reapers.js +2 -1
  39. package/dist/boot/stage-09-leader.d.ts +5 -2
  40. package/dist/boot/stage-10-http-server.d.ts +5 -2
  41. package/dist/boot/stage-10-http-server.js +3 -2
  42. package/dist/boot/stores.d.ts +1 -1
  43. package/dist/boot/stores.js +2 -3
  44. package/dist/boot/web-admin.js +10 -17
  45. package/dist/brain.js +12 -3
  46. package/dist/broker/broker.d.ts +89 -0
  47. package/dist/broker/broker.js +287 -0
  48. package/dist/broker/constants.d.ts +39 -0
  49. package/dist/broker/constants.js +15 -0
  50. package/dist/broker/http1.d.ts +111 -0
  51. package/dist/broker/http1.js +256 -0
  52. package/dist/broker/lease-book.d.ts +87 -0
  53. package/dist/broker/lease-book.js +142 -0
  54. package/dist/broker/lease-file.d.ts +60 -0
  55. package/dist/broker/lease-file.js +127 -0
  56. package/dist/broker/main.d.ts +2 -0
  57. package/dist/broker/main.js +20 -0
  58. package/dist/budget.d.ts +3 -3
  59. package/dist/budget.js +4 -0
  60. package/dist/capabilities/composite-members.d.ts +2 -1
  61. package/dist/capabilities/composite-members.js +2 -1
  62. package/dist/capabilities/engine-capabilities.js +1 -0
  63. package/dist/capabilities/mcp-probe.d.ts +5 -4
  64. package/dist/capabilities/scenarios.d.ts +23 -8
  65. package/dist/capabilities/scenarios.js +13 -8
  66. package/dist/capabilities/skills.d.ts +28 -5
  67. package/dist/capabilities/skills.js +8 -0
  68. package/dist/capability-facts.d.ts +3 -7
  69. package/dist/capability-facts.js +3 -5
  70. package/dist/center-credential-renewer.js +5 -1
  71. package/dist/center-credential.d.ts +18 -4
  72. package/dist/center-credential.js +18 -3
  73. package/dist/checkpoint-ctx-seal-m28.d.ts +3 -10
  74. package/dist/checkpoint-ctx-seal-m28.js +1 -1
  75. package/dist/checkpoint-identity.d.ts +3 -3
  76. package/dist/config-catalog.js +21 -16
  77. package/dist/config-d-migration.js +1 -1
  78. package/dist/config-invariants.d.ts +1 -1
  79. package/dist/config-invariants.js +8 -0
  80. package/dist/config-lkg.d.ts +0 -38
  81. package/dist/config-lkg.js +1 -83
  82. package/dist/config-types.d.ts +21 -4
  83. package/dist/config.d.ts +1 -1
  84. package/dist/config.js +60 -16
  85. package/dist/deployment-governance.d.ts +2 -2
  86. package/dist/deployment-governance.js +7 -9
  87. package/dist/deployment-shape.d.ts +1 -1
  88. package/dist/device-ws-protocol.d.ts +1 -0
  89. package/dist/device-ws-protocol.js +2 -1
  90. package/dist/elicitation.d.ts +8 -7
  91. package/dist/elicitation.js +9 -8
  92. package/dist/file-history-disclosure.d.ts +8 -4
  93. package/dist/file-history-disclosure.js +24 -6
  94. package/dist/fleet-client.d.ts +32 -1
  95. package/dist/fleet-client.js +24 -4
  96. package/dist/fleet-lease.d.ts +7 -3
  97. package/dist/fleet-lease.js +31 -23
  98. package/dist/hooks/classic-stations.d.ts +11 -54
  99. package/dist/hooks/hook-llm.js +14 -8
  100. package/dist/hooks/hook-runner.d.ts +4 -4
  101. package/dist/hooks/hook-runner.js +2 -2
  102. package/dist/http/admission.js +10 -7
  103. package/dist/http/approval-sweeps.js +4 -3
  104. package/dist/http/caller-trust.d.ts +2 -1
  105. package/dist/http/caller-trust.js +1 -3
  106. package/dist/http/checkpoint-ctx.d.ts +76 -16
  107. package/dist/http/checkpoint-ctx.js +45 -1
  108. package/dist/http/dispatch.js +24 -19
  109. package/dist/http/durable-run-leg.d.ts +5 -1
  110. package/dist/http/durable-run-leg.js +1 -1
  111. package/dist/http/health-self-report.d.ts +2 -2
  112. package/dist/http/mcp-remote-text.d.ts +5 -0
  113. package/dist/http/mcp-remote-text.js +7 -0
  114. package/dist/http/principal-gate.d.ts +15 -10
  115. package/dist/http/principal-gate.js +48 -9
  116. package/dist/http/resume-ctx-read.d.ts +2 -16
  117. package/dist/http/resume-ctx-read.js +2 -3
  118. package/dist/http/resume-legs.d.ts +2 -20
  119. package/dist/http/resume-legs.js +52 -38
  120. package/dist/http/route-ctx.d.ts +8 -2
  121. package/dist/http/routes/approvals-assistant.d.ts +0 -2
  122. package/dist/http/routes/approvals-assistant.js +65 -16
  123. package/dist/http/routes/capabilities.js +11 -1
  124. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  125. package/dist/http/routes/mcp-reconnect.js +5 -7
  126. package/dist/http/routes/runs.js +4 -3
  127. package/dist/http/routes/session-sync.js +70 -12
  128. package/dist/http/routes/sessions.js +20 -15
  129. package/dist/http/routes/tasks.js +8 -6
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/stored-body-replay.d.ts +3 -3
  132. package/dist/http/stored-body-replay.js +6 -1
  133. package/dist/http/submit-ctx.js +1 -1
  134. package/dist/http/wire-types.d.ts +57 -30
  135. package/dist/identity-source.d.ts +65 -0
  136. package/dist/identity-source.js +45 -0
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.js +1 -0
  139. package/dist/memory-store-refusal.d.ts +3 -3
  140. package/dist/model-entry-refusal.d.ts +2 -0
  141. package/dist/model-entry-refusal.js +11 -0
  142. package/dist/model-route-gap.d.ts +49 -4
  143. package/dist/model-route-gap.js +34 -1
  144. package/dist/model-select.d.ts +6 -17
  145. package/dist/model-select.js +0 -3
  146. package/dist/namespaced-rule-name.d.ts +1 -1
  147. package/dist/object-store-buckets.d.ts +13 -0
  148. package/dist/object-store-buckets.js +3 -0
  149. package/dist/observability/fail-open.d.ts +19 -7
  150. package/dist/observability/fail-open.js +19 -7
  151. package/dist/observability/metrics.js +2 -2
  152. package/dist/observability/principal-context.d.ts +4 -1
  153. package/dist/observability/principal-context.js +6 -1
  154. package/dist/observability/run-terminal.js +1 -0
  155. package/dist/operator-ask.d.ts +2 -2
  156. package/dist/org-delivery-gate.js +1 -1
  157. package/dist/org-memory-admission.d.ts +2 -2
  158. package/dist/org-memory-admission.js +39 -2
  159. package/dist/parked-decide.d.ts +4 -3
  160. package/dist/parked-decide.js +29 -9
  161. package/dist/peer-directory.d.ts +2 -1
  162. package/dist/peer-directory.js +7 -5
  163. package/dist/permission-rule-list.d.ts +2 -2
  164. package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
  165. package/dist/plugins/approval-ask-store-sql.js +3 -0
  166. package/dist/plugins/blob-backend.js +2 -1
  167. package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
  168. package/dist/plugins/checkpoint-index-columns.js +28 -0
  169. package/dist/plugins/checkpoint-store-sql.d.ts +46 -12
  170. package/dist/plugins/checkpoint-store-sql.js +64 -35
  171. package/dist/plugins/file-history-store-sql.d.ts +6 -0
  172. package/dist/plugins/file-history-store-sql.js +37 -37
  173. package/dist/plugins/file-run-store.js +8 -2
  174. package/dist/plugins/k8s-exec-protocol.d.ts +69 -13
  175. package/dist/plugins/k8s-exec-protocol.js +116 -28
  176. package/dist/plugins/k8s-pod-delete-failure.d.ts +25 -0
  177. package/dist/plugins/k8s-pod-delete-failure.js +17 -0
  178. package/dist/plugins/k8s-pod-reaper.d.ts +112 -0
  179. package/dist/plugins/k8s-pod-reaper.js +122 -0
  180. package/dist/plugins/local-checkpoint-store.d.ts +3 -6
  181. package/dist/plugins/local-checkpoint-store.js +7 -6
  182. package/dist/plugins/memory-run-store.js +2 -0
  183. package/dist/plugins/one-time-migrations.d.ts +49 -37
  184. package/dist/plugins/one-time-migrations.js +87 -128
  185. package/dist/plugins/permission-rule-store-file.js +2 -2
  186. package/dist/plugins/permission-rule-store-sql.d.ts +17 -17
  187. package/dist/plugins/permission-rule-store-sql.js +16 -17
  188. package/dist/plugins/pg-pool.js +4 -0
  189. package/dist/plugins/pg-session-storage.js +2 -2
  190. package/dist/plugins/remote-env-adb.d.ts +3 -0
  191. package/dist/plugins/remote-env-adb.js +1 -0
  192. package/dist/plugins/remote-env-device.d.ts +3 -0
  193. package/dist/plugins/remote-env-device.js +4 -0
  194. package/dist/plugins/remote-env-e2b.d.ts +3 -0
  195. package/dist/plugins/remote-env-e2b.js +1 -0
  196. package/dist/plugins/remote-env-file-error.d.ts +8 -1
  197. package/dist/plugins/remote-env-file-error.js +7 -1
  198. package/dist/plugins/remote-env-host.d.ts +7 -1
  199. package/dist/plugins/remote-env-host.js +7 -1
  200. package/dist/plugins/remote-env-k8s.d.ts +154 -14
  201. package/dist/plugins/remote-env-k8s.js +300 -21
  202. package/dist/plugins/remote-env-local-docker.d.ts +3 -0
  203. package/dist/plugins/remote-env-local-docker.js +1 -0
  204. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  205. package/dist/plugins/remote-env-ssh.js +1 -0
  206. package/dist/plugins/run-store-sql.js +10 -4
  207. package/dist/plugins/s3-answer.d.ts +15 -0
  208. package/dist/plugins/s3-answer.js +5 -0
  209. package/dist/plugins/session-ownership.d.ts +17 -3
  210. package/dist/plugins/session-ownership.js +19 -5
  211. package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
  212. package/dist/plugins/snapshot-bucket-presence.js +35 -7
  213. package/dist/plugins/sql-errors.d.ts +60 -0
  214. package/dist/plugins/sql-errors.js +64 -1
  215. package/dist/plugins/store-contracts.d.ts +10 -1
  216. package/dist/plugins/store-fault.d.ts +4 -0
  217. package/dist/plugins/store-fault.js +10 -0
  218. package/dist/plugins/tidb-pool.js +8 -1
  219. package/dist/plugins/tidb-session-store.js +2 -2
  220. package/dist/plugins/web-search.d.ts +1 -1
  221. package/dist/plugins/web-search.js +2 -2
  222. package/dist/request-declaration-refusal.d.ts +94 -0
  223. package/dist/request-declaration-refusal.js +78 -0
  224. package/dist/request-image-urls.d.ts +6 -0
  225. package/dist/request-image-urls.js +27 -0
  226. package/dist/request-key-closure.d.ts +5 -0
  227. package/dist/request-key-closure.js +5 -2
  228. package/dist/request-value-closure.d.ts +31 -2
  229. package/dist/request-value-closure.js +7 -1
  230. package/dist/rule-entry-list.d.ts +3 -2
  231. package/dist/rule-entry-list.js +2 -2
  232. package/dist/rules-consent.js +7 -4
  233. package/dist/run-local.d.ts +2 -2
  234. package/dist/runs.js +2 -1
  235. package/dist/runtime-caps-resolver.js +1 -1
  236. package/dist/runtime-governance.d.ts +5 -5
  237. package/dist/sandbox-git-lease.d.ts +49 -0
  238. package/dist/sandbox-git-lease.js +60 -0
  239. package/dist/sandbox-git-plane.d.ts +84 -0
  240. package/dist/sandbox-git-plane.js +105 -0
  241. package/dist/sandbox-pkg-source.d.ts +28 -1
  242. package/dist/sandbox-pkg-source.js +1 -2
  243. package/dist/sealed-key.d.ts +19 -0
  244. package/dist/sealed-key.js +47 -4
  245. package/dist/security.d.ts +16 -4
  246. package/dist/security.js +8 -3
  247. package/dist/server-secret-env.d.ts +6 -0
  248. package/dist/server-secret-env.js +14 -0
  249. package/dist/session-error-wire.d.ts +14 -6
  250. package/dist/session-error-wire.js +2 -1
  251. package/dist/session-incarnation.d.ts +16 -5
  252. package/dist/session-incarnation.js +7 -4
  253. package/dist/session-policy-wire.d.ts +1 -1
  254. package/dist/session-policy-wire.js +2 -2
  255. package/dist/session-row.d.ts +3 -17
  256. package/dist/session-row.js +4 -0
  257. package/dist/session-sync.d.ts +15 -3
  258. package/dist/session-sync.js +5 -2
  259. package/dist/spec-fields.d.ts +43 -13
  260. package/dist/spec-fields.js +33 -12
  261. package/dist/stored-request-prompt-profile-m30.d.ts +49 -0
  262. package/dist/stored-request-prompt-profile-m30.js +25 -0
  263. package/dist/task-a2a.d.ts +52 -81
  264. package/dist/task-a2a.js +219 -89
  265. package/dist/task-mcp.d.ts +7 -15
  266. package/dist/task-mcp.js +16 -51
  267. package/dist/task-settings.d.ts +2 -2
  268. package/dist/tool-approval.d.ts +35 -12
  269. package/dist/tool-approval.js +59 -39
  270. package/dist/tool-rule-names.d.ts +5 -0
  271. package/dist/trace/core-keyset-guard.d.ts +5 -5
  272. package/dist/trace/engine-notice-wire.d.ts +1 -1
  273. package/dist/trace/engine-notice-wire.js +2 -0
  274. package/dist/trace/exit-seat.js +37 -13
  275. package/dist/trace/ledger-sink.d.ts +4 -5
  276. package/dist/trace/ledger-sink.js +3 -7
  277. package/dist/trace/project.d.ts +37 -3
  278. package/dist/trace/project.js +20 -6
  279. package/dist/trace/redact.d.ts +40 -12
  280. package/dist/trace/redact.js +332 -128
  281. package/dist/trace/self-minted-wire.d.ts +641 -0
  282. package/dist/trace/self-minted-wire.js +201 -0
  283. package/dist/trace/wire-projection-faces.d.ts +5 -3
  284. package/dist/trace/wire-projection-faces.js +7 -3
  285. package/dist/url-credential-refusal.d.ts +52 -0
  286. package/dist/url-credential-refusal.js +14 -0
  287. package/dist/url-refusal.d.ts +13 -4
  288. package/dist/url-refusal.js +8 -2
  289. package/dist/web-admin-client/apply-effective.js +0 -1
  290. package/dist/web-admin-client/center-request.d.ts +20 -2
  291. package/dist/web-admin-client/center-request.js +40 -5
  292. package/dist/web-admin-client/http-client.js +6 -6
  293. package/dist/web-admin-client/types.d.ts +5 -5
  294. package/dist/workspace-space.d.ts +27 -0
  295. package/dist/workspace-space.js +26 -0
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +11 -5
  297. package/package.json +4 -3
  298. package/scripts/drain-check-local.mjs +2 -1
  299. package/scripts/drain-check.mysql.sql +3 -1
  300. package/scripts/drain-check.pg.sql +3 -1
  301. package/skills/schedule.md +1 -1
  302. package/wire-changes/server@7.104.0.json +134 -0
  303. package/wire-changes/server@7.105.0.json +53 -0
  304. package/wire-changes/server@7.106.0.json +475 -0
  305. package/wire-changes/server@7.107.0.json +64 -0
  306. package/wire-changes/server@7.108.0.json +153 -0
  307. package/wire-changes/server@7.109.0.json +46 -0
  308. package/wire-changes/server@7.110.0.json +90 -0
  309. package/wire-changes/server@7.111.0.json +86 -0
  310. package/wire-changes/server@7.112.0.json +60 -0
  311. package/wire-changes/server@7.113.0.json +26 -0
  312. package/wire-changes/server@7.114.0.json +26 -0
  313. package/wire-changes/server@7.115.0.json +97 -0
  314. package/wire-changes/server@7.116.0.json +218 -0
  315. package/wire-changes/server@7.116.1.json +26 -0
  316. package/wire-changes/server@7.117.0.json +65 -0
  317. package/wire-changes/server@7.118.0.json +129 -0
  318. package/wire-changes/server@7.119.0.json +65 -0
  319. package/wire-changes/server@7.120.0.json +89 -0
  320. package/wire-changes/server@7.121.0.json +219 -0
  321. package/wire-changes/server@7.122.0.json +54 -0
  322. package/wire-changes/server@7.123.0.json +26 -0
  323. package/wire-changes/server@7.124.0.json +336 -0
  324. package/wire-changes/server@7.124.1.json +26 -0
  325. package/wire-changes/server@7.125.0.json +111 -0
  326. package/wire-changes/server@7.126.0.json +285 -0
  327. package/wire-changes/server@7.127.0.json +77 -0
  328. package/wire-changes/server@7.127.1.json +26 -0
  329. package/wire-changes/server@7.127.2.json +53 -0
  330. package/wire-changes/server@7.128.0.json +26 -0
  331. package/wire-changes/server@7.129.0.json +106 -0
  332. package/wire-changes/server@7.129.1.json +26 -0
  333. package/wire-changes/server@7.130.0.json +26 -0
  334. package/wire-changes/server@7.131.0.json +118 -0
  335. package/wire-changes/server@7.132.0.json +226 -0
  336. package/wire-changes/server@7.133.0.json +1155 -0
  337. package/wire-changes/server@7.134.0.json +107 -0
  338. package/wire-changes/server@7.135.0.json +174 -0
  339. package/wire-changes/server@7.136.0.json +448 -0
  340. package/wire-changes/server@7.137.0.json +825 -0
  341. package/dist/url-credential.d.ts +0 -52
  342. package/dist/url-credential.js +0 -49
@@ -1,5 +1,5 @@
1
1
  import { displayUrlOf, stripFormatCharacters } from "@sema-agent/core";
2
- import { CONTROL_AND_BIDI_CHARS, isHierarchicalUrl } from "@sema-agent/settings-schema";
2
+ import { CONTROL_AND_BIDI_CHARS, CREDENTIAL_PARAM_NAMES, isHierarchicalUrl } from "@sema-agent/settings-schema";
3
3
  import { recordFailOpen } from "../observability/fail-open.js";
4
4
  const normalizeForRedaction = stripFormatCharacters;
5
5
  export function mapNormalizedToOriginal(text, normalized) {
@@ -819,13 +819,57 @@ if (/[\]^-]/.test(SCHEME_DELIMITER_CHARS))
819
819
  const SCHEME_DELIMITER_CODES = new Set([...SCHEME_DELIMITER_CHARS].map((c) => c.charCodeAt(0)));
820
820
  const isSchemeDelimiterCode = (c) => SCHEME_DELIMITER_CODES.has(c);
821
821
  const SCHEME_SEPARATOR_CLASS = `[\\s${SCHEME_DELIMITER_CHARS.replace(/\\/g, "\\\\")}]`;
822
- const LEXICAL_KEYWORD_ARMS = new Set();
822
+ function expandKeywordForms(src) {
823
+ let forms = [""];
824
+ for (let i = 0; i < src.length;) {
825
+ let alts;
826
+ const pair = /^\[([A-Za-z])([A-Za-z])\]/.exec(src.slice(i));
827
+ if (pair !== null) {
828
+ if (pair[1].toLowerCase() !== pair[2].toLowerCase())
829
+ throw new Error(`redact: keyword source ${src} holds a class that is not a case pair`);
830
+ alts = [pair[1], pair[2]];
831
+ i += 4;
832
+ }
833
+ else if (/[A-Za-z0-9]/.test(src.charAt(i))) {
834
+ alts = [src.charAt(i)];
835
+ i += 1;
836
+ }
837
+ else {
838
+ throw new Error(`redact: keyword source ${src} holds an atom the head reading cannot enumerate (only letters, digits and [Xx] case pairs)`);
839
+ }
840
+ forms = forms.flatMap((f) => alts.map((a) => f + a));
841
+ }
842
+ const whole = new RegExp(`^(?:${src})$`);
843
+ for (const f of forms)
844
+ if (!whole.test(f))
845
+ throw new Error(`redact: keyword source ${src} does not match its own enumerated form ${f}`);
846
+ return forms;
847
+ }
848
+ const LEXICAL_KEYWORD_ARMS = new Map();
823
849
  const keywordArm = (keywordSrc, valueClass, min, tailSrc = "", separatorClass = "\\s") => {
824
850
  const re = new RegExp(`(?:${keywordSrc}${separatorClass}+${atLeast(valueClass, min)}${tailSrc}` +
825
851
  `|(?<!${WORD_SEGMENT_CLASS})${keywordSrc}(?:${twoStrongAhead(valueClass)}|${noConnectorAhead(valueClass)})${atLeast(valueClass, min)}${tailSrc})`, "g");
826
- LEXICAL_KEYWORD_ARMS.add(re);
852
+ const separator = new RegExp(`^${separatorClass}$`);
853
+ const value = new RegExp(`^${valueClass}$`);
854
+ LEXICAL_KEYWORD_ARMS.set(re, { forms: expandKeywordForms(keywordSrc), min, isSeparator: (u) => separator.test(u), isValue: (u) => value.test(u) });
827
855
  return re;
828
856
  };
857
+ function undecidedKeywordStart(text, t) {
858
+ const n = text.length;
859
+ let valueRun = 0;
860
+ while (valueRun < t.min && valueRun < n && t.isValue(text.charAt(n - 1 - valueRun)))
861
+ valueRun += 1;
862
+ if (valueRun >= t.min)
863
+ return -1;
864
+ let sepStart = n - valueRun;
865
+ while (sepStart > 0 && t.isSeparator(text.charAt(sepStart - 1)))
866
+ sepStart -= 1;
867
+ for (const f of t.forms) {
868
+ if (sepStart >= f.length && text.startsWith(f, sepStart - f.length))
869
+ return sepStart - f.length;
870
+ }
871
+ return -1;
872
+ }
829
873
  const CREDENTIAL_NAME_ANCHORS = {
830
874
  api_key: "structure-only", apikey: "structure-only", "api-key": "structure-only", key: "structure-only",
831
875
  token: "structure-only", access_token: "structure-only", refresh_token: "structure-only", id_token: "structure-only",
@@ -836,15 +880,14 @@ const CREDENTIAL_NAME_ANCHORS = {
836
880
  "x-amz-signature": "structure-only", "x-amz-security-token": "structure-only", "x-amz-credential": "structure-only",
837
881
  sas: "structure-only",
838
882
  };
839
- const CREDENTIAL_NAME_WORDS = Object.keys(CREDENTIAL_NAME_ANCHORS);
840
883
  const CREDENTIAL_NAME_ALT = (() => {
841
- for (const w of CREDENTIAL_NAME_WORDS) {
884
+ for (const w of CREDENTIAL_PARAM_NAMES) {
842
885
  if (!/^[a-z][a-z0-9_-]*$/.test(w))
843
886
  throw new Error(`redact: credential name ${JSON.stringify(w)} must be lowercase [a-z0-9_-] (the alternation is built without escaping)`);
844
887
  }
845
- return [...CREDENTIAL_NAME_WORDS].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).join("|");
888
+ return [...CREDENTIAL_PARAM_NAMES].sort((a, b) => b.length - a.length || (a < b ? -1 : 1)).join("|");
846
889
  })();
847
- const CREDENTIAL_NAME_MAX = Math.max(...CREDENTIAL_NAME_WORDS.map((w) => w.length));
890
+ const CREDENTIAL_NAME_MAX = Math.max(...CREDENTIAL_PARAM_NAMES.map((w) => w.length));
848
891
  const asciiLower = (s) => s.replace(/[A-Z]/g, (c) => c.toLowerCase());
849
892
  function credentialNameAnchor(name) {
850
893
  return CREDENTIAL_ANCHOR_OF.get(asciiLower(name));
@@ -1085,14 +1128,17 @@ function isPemFenceLabel(s, from, to) {
1085
1128
  return false;
1086
1129
  if (!s.startsWith(PEM_KEY_WORD, end - PEM_KEY_WORD.length))
1087
1130
  return false;
1131
+ return !pemLabelDashRuleBroken(s, from, to);
1132
+ }
1133
+ function pemLabelDashRuleBroken(s, from, to) {
1088
1134
  if (s.charCodeAt(from) === 45)
1089
- return false;
1135
+ return true;
1090
1136
  if (s.charCodeAt(from + 1) === 45)
1091
- return false;
1137
+ return true;
1092
1138
  for (let i = from + 1; i < to; i++)
1093
1139
  if (s.charCodeAt(i) === 45 && s.charCodeAt(i + 1) === 45)
1094
- return false;
1095
- return true;
1140
+ return true;
1141
+ return false;
1096
1142
  }
1097
1143
  const PEM_FENCE_OPEN = "-----BEGIN";
1098
1144
  const PEM_FENCE_CLOSE = "-----";
@@ -1137,7 +1183,7 @@ function unitCountWithin(scan, from, to, max) {
1137
1183
  }
1138
1184
  return n <= max;
1139
1185
  }
1140
- function collectSsh2FenceSpans(scan, text, span) {
1186
+ function collectSsh2FenceSpans(scan, text, span, isHead) {
1141
1187
  let cachedFrom = 0;
1142
1188
  let cachedAt;
1143
1189
  const nextClose = (from) => {
@@ -1163,6 +1209,10 @@ function collectSsh2FenceSpans(scan, text, span) {
1163
1209
  continue;
1164
1210
  }
1165
1211
  const bodyFrom = fence + SSH2_FENCE_OPEN.length;
1212
+ if (isHead && unitCountWithin(scan, bodyFrom, text.length, SSH2_BODY_MAX)) {
1213
+ span(fence, text.length, true);
1214
+ return;
1215
+ }
1166
1216
  const close = nextClose(bodyFrom);
1167
1217
  if (close < 0)
1168
1218
  return;
@@ -1177,11 +1227,20 @@ function collectSsh2FenceSpans(scan, text, span) {
1177
1227
  }
1178
1228
  const PEM_END_OPEN = "-----END";
1179
1229
  const PEM_FENCE_BODY_MAX = 8192;
1230
+ const PEM_LINE_INVALID = -1;
1231
+ const PEM_LINE_OPEN = -2;
1180
1232
  function pemFenceLineEnd(text, labelFrom) {
1181
1233
  const close = text.indexOf(PEM_FENCE_CLOSE, labelFrom);
1182
- return close >= 0 && isPemFenceLabel(text, labelFrom, close) ? close + PEM_FENCE_CLOSE.length : -1;
1234
+ if (close < 0) {
1235
+ let to = text.length;
1236
+ while (to > labelFrom && text.charCodeAt(to - 1) === 45)
1237
+ to -= 1;
1238
+ const stillPossible = text.length - to >= 2 ? isPemFenceLabel(text, labelFrom, to) : !pemLabelDashRuleBroken(text, labelFrom, to);
1239
+ return stillPossible ? PEM_LINE_OPEN : PEM_LINE_INVALID;
1240
+ }
1241
+ return isPemFenceLabel(text, labelFrom, close) ? close + PEM_FENCE_CLOSE.length : PEM_LINE_INVALID;
1183
1242
  }
1184
- function collectPemFenceSpans(text, span) {
1243
+ function collectPemFenceSpans(text, span, isHead) {
1185
1244
  let endFrom = -1;
1186
1245
  let endAt = -1;
1187
1246
  let endAfter = -1;
@@ -1201,13 +1260,17 @@ function collectPemFenceSpans(text, span) {
1201
1260
  if (fence < 0)
1202
1261
  return;
1203
1262
  const bodyFrom = pemFenceLineEnd(text, fence + PEM_FENCE_OPEN.length);
1204
- if (bodyFrom < 0) {
1263
+ if (bodyFrom === PEM_LINE_INVALID) {
1205
1264
  from = fence + 1;
1206
1265
  continue;
1207
1266
  }
1208
- earliestEndFence(bodyFrom);
1209
- if (endAt < 0)
1267
+ if (bodyFrom !== PEM_LINE_OPEN)
1268
+ earliestEndFence(bodyFrom);
1269
+ if (bodyFrom === PEM_LINE_OPEN || endAt < 0) {
1270
+ if (isHead)
1271
+ span(fence, text.length, true);
1210
1272
  return;
1273
+ }
1211
1274
  if (endAt - bodyFrom > PEM_FENCE_BODY_MAX) {
1212
1275
  from = fence + 1;
1213
1276
  continue;
@@ -1248,9 +1311,19 @@ function collectPemFragmentSpans(scan, text, json, span) {
1248
1311
  cur = next;
1249
1312
  }
1250
1313
  }
1251
- function collectHeadlessPpkSpans(scan, text, span) {
1314
+ function collectHeadlessPpkSpans(scan, text, span, isHead) {
1252
1315
  const KEY = "Private-Lines: ";
1253
1316
  const n = text.length;
1317
+ const LINE_NONE = -1;
1318
+ const LINE_OPEN = -2;
1319
+ const openAt = (i) => {
1320
+ if (!isHead)
1321
+ return false;
1322
+ let k = i;
1323
+ while (k < n && opaqueUnitEnd(scan, k) < 0 && text.charCodeAt(k) === 92)
1324
+ k += 1;
1325
+ return k >= n;
1326
+ };
1254
1327
  const isB64 = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 43 || c === 47 || c === 61;
1255
1328
  const b64At = (i) => opaqueUnitEnd(scan, i) < 0 && isB64(text.charCodeAt(i));
1256
1329
  const skipHs = (i) => { while (i < n && opaqueUnitEnd(scan, i) < 0 && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9))
@@ -1289,7 +1362,9 @@ function collectHeadlessPpkSpans(scan, text, span) {
1289
1362
  seen = true;
1290
1363
  q = skipHs(q);
1291
1364
  }
1292
- return seen ? q : -1;
1365
+ if (openAt(q))
1366
+ return LINE_OPEN;
1367
+ return seen ? q : LINE_NONE;
1293
1368
  };
1294
1369
  const escapedNewlineLen = (i) => {
1295
1370
  if (opaqueUnitEnd(scan, i) >= 0)
@@ -1304,13 +1379,35 @@ function collectHeadlessPpkSpans(scan, text, span) {
1304
1379
  };
1305
1380
  const atLineEnd = (i) => {
1306
1381
  const b = skipHs(i);
1307
- if (b >= n)
1382
+ if (b >= n || openAt(b))
1308
1383
  return true;
1309
1384
  if (opaqueUnitEnd(scan, b) >= 0)
1310
1385
  return false;
1311
1386
  const c = text.charCodeAt(b);
1312
1387
  return c === 10 || c === 13 || c === 34 || escapedNewlineLen(b) > 0;
1313
1388
  };
1389
+ const bodyLineAt = (from) => {
1390
+ const q0 = skipNewlines(from);
1391
+ if (q0 < 0)
1392
+ return q0;
1393
+ let q = q0;
1394
+ const mk = opaqueUnitEnd(scan, q) < 0 ? text.charCodeAt(q) : -1;
1395
+ if (mk === 43 || mk === 45)
1396
+ q = skipHs(q + 1);
1397
+ let r = q;
1398
+ while (r < n && b64At(r))
1399
+ r += 1;
1400
+ if (r - q < 4) {
1401
+ if (isHead && r >= n)
1402
+ return LINE_OPEN;
1403
+ r = q0;
1404
+ while (r < n && b64At(r))
1405
+ r += 1;
1406
+ if (r - q0 < 4)
1407
+ return LINE_NONE;
1408
+ }
1409
+ return atLineEnd(r) ? r : LINE_NONE;
1410
+ };
1314
1411
  let search = 0;
1315
1412
  for (;;) {
1316
1413
  const at = text.indexOf(KEY, search);
@@ -1324,40 +1421,28 @@ function collectHeadlessPpkSpans(scan, text, span) {
1324
1421
  const d0 = p;
1325
1422
  while (p < n && opaqueUnitEnd(scan, p) < 0 && text.charCodeAt(p) >= 48 && text.charCodeAt(p) <= 57)
1326
1423
  p += 1;
1327
- if (p === d0) {
1424
+ if (p === d0 && p < n) {
1328
1425
  search = at + KEY.length;
1329
1426
  continue;
1330
1427
  }
1331
- let end = p;
1332
- let lines = 0;
1333
- for (;;) {
1334
- const q0 = skipNewlines(end);
1335
- if (q0 === -1)
1428
+ let end = -1;
1429
+ let open = false;
1430
+ for (let from = p;;) {
1431
+ const r = bodyLineAt(from);
1432
+ if (r === LINE_NONE)
1336
1433
  break;
1337
- let q = q0;
1338
- const mk = opaqueUnitEnd(scan, q) < 0 ? text.charCodeAt(q) : -1;
1339
- if (mk === 43 || mk === 45)
1340
- q = skipHs(q + 1);
1341
- let r = q;
1342
- while (r < n && b64At(r))
1343
- r += 1;
1344
- if (r - q < 4) {
1345
- r = q0;
1346
- while (r < n && b64At(r))
1347
- r += 1;
1348
- if (r - q0 < 4)
1349
- break;
1350
- }
1351
- if (!atLineEnd(r))
1434
+ end = r === LINE_OPEN ? n : r;
1435
+ if (r === LINE_OPEN) {
1436
+ open = true;
1352
1437
  break;
1353
- lines += 1;
1354
- end = r;
1438
+ }
1439
+ from = r;
1355
1440
  }
1356
- if (lines === 0) {
1441
+ if (end < 0) {
1357
1442
  search = p;
1358
1443
  continue;
1359
1444
  }
1360
- span(at, end);
1445
+ span(at, end, open);
1361
1446
  search = end;
1362
1447
  }
1363
1448
  }
@@ -1442,10 +1527,6 @@ const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c =
1442
1527
  const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
1443
1528
  const isControlCode = (c) => c < 0x20 || c === 0x7f;
1444
1529
  const TOKEN_UNIT_Y = new RegExp(REDACTION_TOKEN_RE_SRC, "y");
1445
- export const CREDENTIAL_PARAM_NAMES = Object.freeze([...CREDENTIAL_NAME_WORDS]);
1446
- export function isCredentialParamName(decodedName) {
1447
- return credentialNameAnchor(decodedName) !== undefined;
1448
- }
1449
1530
  const CREDENTIAL_NAME_SCAN_MAX = CREDENTIAL_NAME_MAX * 3 + 1;
1450
1531
  function decodeAsciiName(name) {
1451
1532
  let out = "";
@@ -1757,10 +1838,12 @@ function scanUrlRun(scan, text, i, kind, json) {
1757
1838
  noteEvidenceRead(scan, i);
1758
1839
  return i;
1759
1840
  }
1760
- function matchUserinfoAt(scan, text, p, json) {
1841
+ function matchUserinfoAt(scan, text, p, json, isHead) {
1761
1842
  const probeEnd = scanUrlRun(scan, text, p, "plain-user", json);
1762
1843
  const keyId = probeEnd > p && text.charCodeAt(probeEnd) === 58 && credentialSpansCover(scan, p, probeEnd);
1763
- scanUrlRun(scan, text, p, keyId ? "token-pass" : "plain-pass", json);
1844
+ const runEnd = scanUrlRun(scan, text, p, keyId ? "token-pass" : "plain-pass", json);
1845
+ if (isHead && runEnd >= text.length && runEnd > p)
1846
+ return runEnd;
1764
1847
  const at = scan.lastAt;
1765
1848
  if (at <= p)
1766
1849
  return null;
@@ -1778,7 +1861,7 @@ function growUrlSegments(list) {
1778
1861
  list.a = bigger;
1779
1862
  return true;
1780
1863
  }
1781
- function collectUrlCredentialSpans(scan, text, json, span) {
1864
+ function collectUrlCredentialSpans(scan, text, json, span, isHead) {
1782
1865
  const dirty = scan.dirty;
1783
1866
  const hr = scan.headsRead;
1784
1867
  const hw = scan.headsWrite;
@@ -1814,12 +1897,12 @@ function collectUrlCredentialSpans(scan, text, json, span) {
1814
1897
  else {
1815
1898
  scan.footLo = FOOT_NONE;
1816
1899
  scan.footHi = -1;
1817
- at = matchUserinfoAt(scan, text, start, json) ?? -1;
1900
+ at = matchUserinfoAt(scan, text, start, json, isHead) ?? -1;
1818
1901
  lo = scan.footLo;
1819
1902
  hi = scan.footHi;
1820
1903
  urlSegmentRuns += 1;
1821
1904
  if (at >= 0 && at !== (known ? ph[pc + 1] : -2))
1822
- span(start, at);
1905
+ span(start, at, isHead && at >= text.length);
1823
1906
  }
1824
1907
  if (hw.n + URL_SEG_STRIDE <= hw.a.length || growUrlSegments(hw)) {
1825
1908
  const w = hw.a;
@@ -1915,7 +1998,8 @@ function jsonSecretFieldValueQuoteAt(text, p) {
1915
1998
  i += 1;
1916
1999
  return text.charCodeAt(i) === CH_QUOTE ? i : -1;
1917
2000
  }
1918
- function jsonStringEndAt(text, q) {
2001
+ const STRING_OPEN = -2;
2002
+ function jsonStringEndAt(text, q, isHead) {
1919
2003
  const n = text.length;
1920
2004
  for (let i = q + 1; i < n;) {
1921
2005
  const c = text.charCodeAt(i);
@@ -1926,21 +2010,23 @@ function jsonStringEndAt(text, q) {
1926
2010
  i += 2;
1927
2011
  continue;
1928
2012
  }
1929
- return -1;
2013
+ return isHead && i + 1 >= n ? STRING_OPEN : -1;
1930
2014
  }
1931
2015
  i += 1;
1932
2016
  }
1933
- return -1;
2017
+ return isHead ? STRING_OPEN : -1;
1934
2018
  }
1935
- function collectJsonSecretFieldSpans(text, span) {
2019
+ function collectJsonSecretFieldSpans(text, span, isHead) {
1936
2020
  for (let p = text.indexOf(JSON_FIELD_ANCHOR); p >= 0;) {
1937
2021
  const q = jsonSecretFieldValueQuoteAt(text, p);
1938
- const end = q < 0 ? -1 : jsonStringEndAt(text, q);
1939
- if (end < 0) {
2022
+ const found = q < 0 ? -1 : jsonStringEndAt(text, q, isHead);
2023
+ if (found === -1) {
1940
2024
  p = text.indexOf(JSON_FIELD_ANCHOR, p + 1);
1941
2025
  continue;
1942
2026
  }
1943
- span(q, end);
2027
+ const open = found === STRING_OPEN;
2028
+ const end = open ? text.length : found;
2029
+ span(q, end, open);
1944
2030
  p = text.indexOf(JSON_FIELD_ANCHOR, scanResumeAfterHit(p, end, JSON_FIELD_ANCHOR.length));
1945
2031
  }
1946
2032
  }
@@ -2088,7 +2174,7 @@ function anchorHeadAt(text, op, json) {
2088
2174
  }
2089
2175
  return { strength, quotedName: quote !== 0, spacedLeft, listSep };
2090
2176
  }
2091
- function quotedValueEnd(text, cs, quote, json) {
2177
+ function quotedValueEnd(text, cs, quote, json, isHead) {
2092
2178
  const n = text.length;
2093
2179
  let i = cs;
2094
2180
  while (i < n) {
@@ -2097,18 +2183,18 @@ function quotedValueEnd(text, cs, quote, json) {
2097
2183
  if (ch === quote)
2098
2184
  return i;
2099
2185
  if (ch === JSON_STRING_END)
2100
- break;
2186
+ return -1;
2101
2187
  i += unitLenOf(unit);
2102
2188
  if (ch === CH_BACKSLASH && quote !== JSON_STRING_END) {
2103
2189
  if (i >= n)
2104
2190
  break;
2105
2191
  const escaped = readAnchorUnit(text, i, json);
2106
2192
  if (unitChOf(escaped) === JSON_STRING_END)
2107
- break;
2193
+ return -1;
2108
2194
  i += unitLenOf(escaped);
2109
2195
  }
2110
2196
  }
2111
- return -1;
2197
+ return isHead ? STRING_OPEN : -1;
2112
2198
  }
2113
2199
  function bareValueEnd(text, s, json, listSep) {
2114
2200
  const n = text.length;
@@ -2153,7 +2239,7 @@ function bareValueEnd(text, s, json, listSep) {
2153
2239
  }
2154
2240
  return e;
2155
2241
  }
2156
- function literalValueAt(text, s, json, declaredIfBare, listSep) {
2242
+ function literalValueAt(text, s, json, declaredIfBare, listSep, isHead) {
2157
2243
  const n = text.length;
2158
2244
  if (s >= n)
2159
2245
  return undefined;
@@ -2166,7 +2252,9 @@ function literalValueAt(text, s, json, declaredIfBare, listSep) {
2166
2252
  return undefined;
2167
2253
  if (!isAsciiAlnumCode(unitChOf(readAnchorUnit(text, vs, json))))
2168
2254
  return undefined;
2169
- const close = quotedValueEnd(text, vs, first, json);
2255
+ const close = quotedValueEnd(text, vs, first, json, isHead);
2256
+ if (close === STRING_OPEN)
2257
+ return { start: vs, end: n, quoted: true, open: true };
2170
2258
  if (close >= 0)
2171
2259
  return { start: vs, end: close, quoted: true };
2172
2260
  }
@@ -2234,7 +2322,7 @@ function schemeValueAt(text, s, json) {
2234
2322
  return undefined;
2235
2323
  return { start: vs, end: e, quoted: false };
2236
2324
  }
2237
- function collectAnchoredValueSpans(text, json, span) {
2325
+ function collectAnchoredValueSpans(text, json, span, isHead) {
2238
2326
  const n = text.length;
2239
2327
  let resume = 0;
2240
2328
  let nextEq = text.indexOf("=");
@@ -2275,10 +2363,10 @@ function collectAnchoredValueSpans(text, json, span) {
2275
2363
  v = schemeValueAt(text, s, json);
2276
2364
  break;
2277
2365
  case "declarative":
2278
- v = literalValueAt(text, s, json, head.quotedName || (!head.spacedLeft && !spacedRight), head.listSep);
2366
+ v = literalValueAt(text, s, json, head.quotedName || (!head.spacedLeft && !spacedRight), head.listSep, isHead);
2279
2367
  break;
2280
2368
  case "any-form":
2281
- v = literalValueAt(text, s, json, true, head.listSep);
2369
+ v = literalValueAt(text, s, json, true, head.listSep, isHead);
2282
2370
  break;
2283
2371
  default: {
2284
2372
  const unhandled = head.strength;
@@ -2287,14 +2375,14 @@ function collectAnchoredValueSpans(text, json, span) {
2287
2375
  }
2288
2376
  if (v === undefined)
2289
2377
  continue;
2290
- span(v.start, v.end);
2378
+ span(v.start, v.end, v.open === true);
2291
2379
  resume = v.quoted ? v.start : v.end;
2292
2380
  }
2293
2381
  }
2294
2382
  export function structuralScannerForTest(arm) {
2295
2383
  const collect = (scan) => (text) => {
2296
2384
  const out = [];
2297
- scan(text, (start, end) => out.push([start, end]));
2385
+ scan(text, (start, end) => out.push([start, end]), false);
2298
2386
  return out;
2299
2387
  };
2300
2388
  switch (arm) {
@@ -2307,7 +2395,7 @@ export function structuralScannerForTest(arm) {
2307
2395
  let ppkScanBytes = 0;
2308
2396
  export function ppkScanBytesForTest() { return ppkScanBytes; }
2309
2397
  export function resetPpkScanBytesForTest() { ppkScanBytes = 0; }
2310
- function collectPpkContainerSpans(scan, text, eat, span) {
2398
+ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
2311
2399
  const HEAD = "PuTTY-User-Key-File-";
2312
2400
  const MAC = "Private-MAC: ";
2313
2401
  const HASH = "Private-Hash: ";
@@ -2323,9 +2411,8 @@ function collectPpkContainerSpans(scan, text, eat, span) {
2323
2411
  cache.set(needle, v);
2324
2412
  return v;
2325
2413
  };
2326
- const isVersionedHead = (at) => {
2414
+ const versionEnd = (at) => {
2327
2415
  let p = at + HEAD.length;
2328
- const start = p;
2329
2416
  while (p < text.length && opaqueUnitEnd(scan, p) < 0) {
2330
2417
  const c = text.charCodeAt(p);
2331
2418
  if (c >= 48 && c <= 57)
@@ -2333,7 +2420,15 @@ function collectPpkContainerSpans(scan, text, eat, span) {
2333
2420
  else
2334
2421
  break;
2335
2422
  }
2336
- return p > start && text.startsWith(": ", p);
2423
+ return p;
2424
+ };
2425
+ const isVersionedHead = (at) => {
2426
+ const p = versionEnd(at);
2427
+ return p > at + HEAD.length && text.startsWith(": ", p);
2428
+ };
2429
+ const isOpenHead = (at) => {
2430
+ const p = versionEnd(at);
2431
+ return p >= text.length || (p === text.length - 1 && p > at + HEAD.length && text.charCodeAt(p) === 58);
2337
2432
  };
2338
2433
  const nextVersionedHead = (from) => {
2339
2434
  let p = find(HEAD, from);
@@ -2356,13 +2451,16 @@ function collectPpkContainerSpans(scan, text, eat, span) {
2356
2451
  while (at !== Number.POSITIVE_INFINITY) {
2357
2452
  let pos = at + HEAD.length + 1;
2358
2453
  let end = text.length;
2359
- let ateToEnd = true;
2454
+ let ateToEnd = !isHead;
2455
+ let open = false;
2360
2456
  for (;;) {
2361
2457
  const mac = find(MAC, pos);
2362
2458
  const hash = find(HASH, pos);
2363
2459
  const term = Math.min(mac, hash);
2364
- if (term === Number.POSITIVE_INFINITY)
2460
+ if (term === Number.POSITIVE_INFINITY) {
2461
+ open = isHead;
2365
2462
  break;
2463
+ }
2366
2464
  const hexStart = term + (term === mac ? MAC.length : HASH.length);
2367
2465
  const hexEnd = hexRunEnd(hexStart);
2368
2466
  if (hexEnd - hexStart < 32) {
@@ -2389,17 +2487,22 @@ function collectPpkContainerSpans(scan, text, eat, span) {
2389
2487
  }
2390
2488
  }
2391
2489
  }
2490
+ const undecided = isHead && limit === Number.POSITIVE_INFINITY;
2392
2491
  ateToEnd = straggler < limit || !gapBlank;
2393
- end = ateToEnd ? text.length : hexEnd;
2492
+ end = ateToEnd || undecided ? text.length : hexEnd;
2493
+ open = undecided;
2394
2494
  break;
2395
2495
  }
2396
2496
  if (ateToEnd && eat !== undefined && eat.from < 0) {
2397
2497
  eat.from = at;
2398
2498
  eat.len = text.length;
2399
2499
  }
2400
- span(at, end);
2500
+ span(at, end, open);
2401
2501
  at = end >= text.length ? Number.POSITIVE_INFINITY : nextVersionedHead(end);
2402
2502
  }
2503
+ const last = isHead ? text.lastIndexOf(HEAD) : -1;
2504
+ if (last >= 0 && !inOpaqueUnit(scan, last) && isOpenHead(last))
2505
+ span(last, text.length, true);
2403
2506
  }
2404
2507
  const armPool = (() => {
2405
2508
  const spanRepl = (token) => {
@@ -2421,6 +2524,7 @@ const armPool = (() => {
2421
2524
  throw new Error(`redact: boundary arm /${re.source}/ has a zero-length structural head — the bounded head screen would admit every candidate position`);
2422
2525
  }
2423
2526
  const arm = { g: re, y, back, headSrc, headFirst, repl, rank, order };
2527
+ const keywordTail = LEXICAL_KEYWORD_ARMS.get(re);
2424
2528
  const boundary = y === undefined || headSrc === undefined || headFirst === undefined ? undefined : { ...arm, y, headSrc, headFirst };
2425
2529
  return {
2426
2530
  token,
@@ -2428,9 +2532,17 @@ const armPool = (() => {
2428
2532
  order,
2429
2533
  phase: 1,
2430
2534
  lexical: LEXICAL_KEYWORD_ARMS.has(re),
2431
- collect: boundary === undefined
2432
- ? (text, _json, _eat, out) => collectRegexSpans(arm, text, out)
2433
- : (text, _json, _eat, out) => collectValueArmSpans(boundary, text, out),
2535
+ collect: (text, _json, _eat, out, isHead) => {
2536
+ if (boundary === undefined)
2537
+ collectRegexSpans(arm, text, out);
2538
+ else
2539
+ collectValueArmSpans(boundary, text, out);
2540
+ if (isHead && keywordTail !== undefined) {
2541
+ const at = undecidedKeywordStart(text, keywordTail);
2542
+ if (at >= 0)
2543
+ out.push({ start: at, end: text.length, repl, rank, order, undecided: true });
2544
+ }
2545
+ },
2434
2546
  retry: boundary === undefined ? undefined : (text, at, out) => collectStickySpans(boundary, text, at, out),
2435
2547
  headSrc: boundary === undefined ? undefined : headSrc,
2436
2548
  headFirst: boundary === undefined ? undefined : headFirst,
@@ -2444,7 +2556,7 @@ const armPool = (() => {
2444
2556
  order,
2445
2557
  phase: 1,
2446
2558
  lexical,
2447
- collect: (text, json, eat, out) => arm(text, json, eat, (start, end) => out.push({ start, end, repl, rank, order })),
2559
+ 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),
2448
2560
  retry: undefined,
2449
2561
  headSrc: undefined,
2450
2562
  headFirst: undefined,
@@ -2459,7 +2571,7 @@ const armPool = (() => {
2459
2571
  order,
2460
2572
  phase: 2,
2461
2573
  lexical: false,
2462
- collect: (scan, text, json, eat, out) => arm(scan, text, json, eat, (start, end) => out.push({ start, end, repl, rank, order })),
2574
+ 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),
2463
2575
  retry: undefined,
2464
2576
  headSrc: undefined,
2465
2577
  headFirst: undefined,
@@ -2467,22 +2579,22 @@ const armPool = (() => {
2467
2579
  };
2468
2580
  const structuralScannerMember = (row, order) => {
2469
2581
  if (row === PEM_FENCE_PATTERN)
2470
- return scannerMember(row[1], order, (text, _json, _eat, span) => collectPemFenceSpans(text, span));
2582
+ return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectPemFenceSpans(text, span, isHead));
2471
2583
  if (row === SSH2_FENCE_PATTERN)
2472
- return forensicMember(row[1], order, (scan, text, _json, _eat, span) => collectSsh2FenceSpans(scan, text, span));
2584
+ return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, span, isHead));
2473
2585
  if (row === URL_CREDENTIAL_PATTERN)
2474
- return forensicMember(row[1], order, (scan, text, json, _eat, span) => collectUrlCredentialSpans(scan, text, json, span));
2586
+ return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead));
2475
2587
  if (row === JSON_SECRET_FIELD_PATTERN)
2476
- return scannerMember(row[1], order, (text, _json, _eat, span) => collectJsonSecretFieldSpans(text, span));
2588
+ return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead));
2477
2589
  throw new Error(`redact: structural row /${row[0].source}/ has no hand-written scanner — structural grammars are declared as regexes but never executed as one (S-102 / S-265)`);
2478
2590
  };
2479
2591
  const members = [];
2480
2592
  for (const row of STRUCTURAL_SECRET_PATTERNS)
2481
2593
  members.push(structuralScannerMember(row, members.length));
2482
- members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span) => collectPpkContainerSpans(scan, text, eat, span)));
2594
+ members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead)));
2483
2595
  members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span) => collectPemFragmentSpans(scan, text, json, span)));
2484
- members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span) => collectHeadlessPpkSpans(scan, text, span)));
2485
- members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span) => collectAnchoredValueSpans(text, json, span), ARM_RANK.keyword, true));
2596
+ members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead)));
2597
+ members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span, isHead) => collectAnchoredValueSpans(text, json, span, isHead), ARM_RANK.keyword, true));
2486
2598
  for (const row of KEYWORD_SECRET_PATTERNS)
2487
2599
  members.push(regexMember(row, ARM_RANK.keyword, members.length));
2488
2600
  for (const row of SHAPE_SECRET_PATTERNS)
@@ -2513,8 +2625,9 @@ const armPool = (() => {
2513
2625
  const headFirstUnit = unionMembers(retryMembers.map((m) => m.headFirst)) ?? new Uint32Array(0x10000 >>> 5).fill(0xffffffff);
2514
2626
  poolArmCount = members.length;
2515
2627
  poolBoundaryArmCount = retryMembers.length;
2516
- return (text, wantCount, json, eat, scope) => {
2628
+ return (text, wantCount, json, eat, scope, reach) => {
2517
2629
  readings += 1;
2630
+ const isHead = reach !== undefined;
2518
2631
  const lane = LANES[scope];
2519
2632
  const spans = [];
2520
2633
  let tried;
@@ -2550,7 +2663,7 @@ const armPool = (() => {
2550
2663
  }
2551
2664
  };
2552
2665
  for (const m of lane.phase1)
2553
- m.collect(text, json, eat, spans);
2666
+ m.collect(text, json, eat, spans, isHead);
2554
2667
  closeCoveragePositions(0);
2555
2668
  if (lane.phase2.length > 0) {
2556
2669
  const scan = buildForensicScan(credentialSpans(text), lane.phase2.length);
@@ -2567,7 +2680,7 @@ const armPool = (() => {
2567
2680
  }
2568
2681
  scan.footLo = FOOT_NONE;
2569
2682
  scan.footHi = -1;
2570
- lane.phase2[j].collect(scan, text, json, level === 0 ? eat : undefined, spans);
2683
+ lane.phase2[j].collect(scan, text, json, level === 0 ? eat : undefined, spans, isHead);
2571
2684
  foot[2 * j] = scan.footLo;
2572
2685
  foot[2 * j + 1] = scan.footHi;
2573
2686
  forensicRuns += 1;
@@ -2582,11 +2695,12 @@ const armPool = (() => {
2582
2695
  }
2583
2696
  }
2584
2697
  poolSpanCount += spans.length;
2585
- const kept = resolveSpanOverlaps(spans);
2698
+ const survives = (sp) => sp.undecided !== true || reach === undefined || reach(sp.start);
2699
+ const kept = resolveSpanOverlaps(reach === undefined ? spans : spans.filter(survives));
2586
2700
  if (!wantCount || kept.length === 0)
2587
2701
  return { hits: NO_HITS, kept };
2588
2702
  const tokenOf = (sp) => members[sp.order].token;
2589
- const strong = spans.filter((sp) => armOfToken(tokenOf(sp)) === "full").sort((a, b) => a.start - b.start);
2703
+ const strong = spans.filter((sp) => armOfToken(tokenOf(sp)) === "full" && survives(sp)).sort((a, b) => a.start - b.start);
2590
2704
  const byArmedToken = new Map();
2591
2705
  const stamped = [];
2592
2706
  let si = 0;
@@ -2613,8 +2727,8 @@ let onRedaction;
2613
2727
  export function setRedactionObserver(fn) {
2614
2728
  onRedaction = fn;
2615
2729
  }
2616
- function applyRules(text, wantCount, json, eat, scope) {
2617
- return armPool(text, wantCount, json, eat, scope);
2730
+ function applyRules(text, wantCount, json, eat, scope, reach) {
2731
+ return armPool(text, wantCount, json, eat, scope, reach);
2618
2732
  }
2619
2733
  function reportHits(hits) {
2620
2734
  if (onRedaction === undefined)
@@ -2627,6 +2741,7 @@ function reportHits(hits) {
2627
2741
  }
2628
2742
  }
2629
2743
  }
2744
+ export const WHOLE_EXTENT = { kind: "whole" };
2630
2745
  function invisibleClassesOf(layout) {
2631
2746
  switch (layout) {
2632
2747
  case "multi-line":
@@ -2639,7 +2754,99 @@ function invisibleClassesOf(layout) {
2639
2754
  }
2640
2755
  }
2641
2756
  }
2642
- function runRedaction(text, observe, json, wantHits, scope, layout) {
2757
+ function trailingPartialEscapeStart(s) {
2758
+ const n = s.length;
2759
+ const runStart = (end) => {
2760
+ let k = end;
2761
+ while (k > 0 && s.charCodeAt(k - 1) === CH_BACKSLASH)
2762
+ k -= 1;
2763
+ return k;
2764
+ };
2765
+ const highSurrogateEscapeStartBefore = (end) => {
2766
+ let j = end;
2767
+ let value = -1;
2768
+ let bodyStart = -1;
2769
+ if (j >= 1 && s.charCodeAt(j - 1) === CH_RBRACE) {
2770
+ let k = j - 1;
2771
+ while (k > 0 && hexDigitValue(s.charCodeAt(k - 1)) >= 0)
2772
+ k -= 1;
2773
+ if (k >= 2 && s.charCodeAt(k - 1) === CH_LBRACE && s.charCodeAt(k - 2) === CH_u && j - 1 - k >= 1) {
2774
+ value = readHexRunValue(s, k, j - 1);
2775
+ bodyStart = k - 2;
2776
+ }
2777
+ }
2778
+ else if (j >= 5 && s.charCodeAt(j - 5) === CH_u) {
2779
+ value = readHexRun(s, j - 4, 4);
2780
+ bodyStart = j - 5;
2781
+ }
2782
+ if (value < 0xd800 || value > 0xdbff)
2783
+ return -1;
2784
+ const k = runStart(bodyStart);
2785
+ return k < bodyStart ? k : -1;
2786
+ };
2787
+ let j = n;
2788
+ while (j > 0 && hexDigitValue(s.charCodeAt(j - 1)) >= 0)
2789
+ j -= 1;
2790
+ const hexRun = n - j;
2791
+ let element = -1;
2792
+ if (j >= 2 && s.charCodeAt(j - 1) === CH_LBRACE && s.charCodeAt(j - 2) === CH_u)
2793
+ element = j - 2;
2794
+ else if (j >= 1 && s.charCodeAt(j - 1) === CH_u && hexRun <= 3)
2795
+ element = j - 1;
2796
+ else if (j >= 1 && s.charCodeAt(j - 1) === CH_x && hexRun <= 1)
2797
+ element = j - 1;
2798
+ else if (hexRun === 0 && n >= 1 && s.charCodeAt(n - 1) === CH_BACKSLASH)
2799
+ element = n;
2800
+ if (element >= 0) {
2801
+ const k = runStart(element);
2802
+ if (k >= element)
2803
+ return -1;
2804
+ const high = highSurrogateEscapeStartBefore(k);
2805
+ return high >= 0 ? high : k;
2806
+ }
2807
+ return highSurrogateEscapeStartBefore(n);
2808
+ }
2809
+ function readHexRunValue(s, from, to) {
2810
+ let v = 0;
2811
+ for (let k = from; k < to; k++) {
2812
+ const d = hexDigitValue(s.charCodeAt(k));
2813
+ if (d < 0)
2814
+ return -1;
2815
+ v = v * 16 + d;
2816
+ if (v > 0x10ffff)
2817
+ return -1;
2818
+ }
2819
+ return v;
2820
+ }
2821
+ function shortestReading(text, layout) {
2822
+ const raw = collectRawControlCuts(text, invisibleClassesOf(layout));
2823
+ const normalized = normalizeForRedaction(deleteInvisibleCharacters(text, raw, raw.present).text);
2824
+ const open = trailingPartialEscapeStart(normalized);
2825
+ const spelled = collectSpelledCuts(normalized);
2826
+ const length = deleteInvisibleCharacters(normalized, spelled, spelled.present).text.length;
2827
+ return open < 0 ? { visible: length, partial: false } : { visible: length - (normalized.length - open), partial: true };
2828
+ }
2829
+ export function readingHeadEnd(text, layout, units, from = 0) {
2830
+ const rest = text.length - from;
2831
+ let len = Math.min(rest, units);
2832
+ let first = true;
2833
+ let grow = 64;
2834
+ while (len < rest) {
2835
+ const { visible, partial } = shortestReading(text.slice(from, from + len), layout);
2836
+ if (visible >= units) {
2837
+ if (!partial)
2838
+ return from + len;
2839
+ len = Math.min(rest, len + grow);
2840
+ grow *= 2;
2841
+ continue;
2842
+ }
2843
+ const step = first && visible * 2 >= len ? Math.ceil((units - visible) / (visible / len)) + 1 : Math.max(units - visible, len);
2844
+ first = false;
2845
+ len = Math.min(rest, len + step);
2846
+ }
2847
+ return text.length;
2848
+ }
2849
+ function runRedaction(text, observe, json, wantHits, scope, layout, extent) {
2643
2850
  const wantCount = wantHits || (onRedaction !== undefined && observe);
2644
2851
  const eat = observe ? { from: -1, len: 0 } : undefined;
2645
2852
  let spans = [];
@@ -2647,15 +2854,17 @@ function runRedaction(text, observe, json, wantHits, scope, layout) {
2647
2854
  let readsSoFar = 0;
2648
2855
  let unioned = false;
2649
2856
  const read = (layers, view) => {
2650
- const r = applyRules(view, wantCount, json, eat, scope);
2857
+ const toOriginal = createViewToOriginal(layers);
2858
+ const reach = extent.kind === "head" ? (viewStart) => toOriginal.start(viewStart) < extent.emitEnd : undefined;
2859
+ const r = applyRules(view, wantCount, json, eat, scope, reach);
2651
2860
  forgetEscapedSeparatorTails();
2652
2861
  readsSoFar += 1;
2653
2862
  if (readsSoFar === 1) {
2654
- spans = spansInOriginal(layers, r.kept);
2863
+ spans = toOriginal.spans(r.kept);
2655
2864
  hits = r.hits;
2656
2865
  return;
2657
2866
  }
2658
- const extra = spansInOriginal(layers, r.kept);
2867
+ const extra = toOriginal.spans(r.kept);
2659
2868
  if (extra.length === 0)
2660
2869
  return;
2661
2870
  spans = resolveSpanOverlaps([...spans, ...extra]);
@@ -2697,41 +2906,36 @@ function hitsOfSpans(spans) {
2697
2906
  }
2698
2907
  return [...by.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm }));
2699
2908
  }
2700
- function spansInOriginal(layers, kept) {
2701
- let out = kept;
2702
- for (const layer of layers) {
2703
- if (out.length === 0)
2704
- return out;
2705
- let at;
2909
+ function createViewToOriginal(layers) {
2910
+ let ats;
2911
+ const layerMaps = () => (ats ??= layers.flatMap((layer) => {
2706
2912
  if ("fold" in layer) {
2707
2913
  const fold = layer.fold;
2708
- if (fold.at === undefined)
2709
- continue;
2710
- at = (v) => viewIndexInInput(fold, v);
2914
+ return fold.at === undefined ? [] : [(v) => viewIndexInInput(fold, v)];
2711
2915
  }
2712
- else {
2713
- if (layer.view === layer.input)
2714
- continue;
2715
- const map = mapNormalizedToOriginal(layer.input, layer.view);
2716
- at = (v) => map[v];
2717
- }
2718
- out = out.map((s) => ({ ...s, start: at(s.start), end: at(s.end - 1) + 1 }));
2719
- }
2720
- return out;
2916
+ if (layer.view === layer.input)
2917
+ return [];
2918
+ const map = mapNormalizedToOriginal(layer.input, layer.view);
2919
+ return [(v) => map[v]];
2920
+ }));
2921
+ return {
2922
+ start: (viewIndex) => layerMaps().reduce((p, at) => at(p), viewIndex),
2923
+ spans: (kept) => (kept.length === 0 ? kept : layerMaps().reduce((out, at) => out.map((s) => ({ ...s, start: at(s.start), end: at(s.end - 1) + 1 })), kept)),
2924
+ };
2721
2925
  }
2722
2926
  export function redactSecrets(text, opts) {
2723
- return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text", "multi-line").text;
2927
+ return runRedaction(text, opts?.observe ?? true, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT).text;
2724
2928
  }
2725
2929
  export function redactSecretsWithArms(text, opts) {
2726
- const r = runRedaction(text, opts?.observe ?? true, opts?.json === true, true, "free-text", "multi-line");
2930
+ const r = runRedaction(text, opts?.observe ?? true, opts?.json === true, true, "free-text", "multi-line", WHOLE_EXTENT);
2727
2931
  return { text: r.text, arms: new Set(r.hits.map((h) => h.arm)) };
2728
2932
  }
2729
2933
  export function redactionSpansForTest(text, opts) {
2730
- const r = runRedaction(text, false, opts?.json === true, false, "free-text", "multi-line");
2934
+ const r = runRedaction(text, false, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT);
2731
2935
  return { text: r.text, spans: r.spans.map((sp) => ({ start: sp.start, end: sp.end })) };
2732
2936
  }
2733
- export function proposeRedactionSpans(text, scope, layout) {
2734
- return runRedaction(text, true, false, false, scope, layout).spans.map((sp) => ({ start: sp.start, end: sp.end, repl: sp.repl }));
2937
+ export function proposeRedactionSpans(text, scope, layout, extent) {
2938
+ return runRedaction(text, true, false, false, scope, layout, extent).spans.map((sp) => ({ start: sp.start, end: sp.end, repl: sp.repl }));
2735
2939
  }
2736
2940
  const REDACT_MAX_DEPTH = 64;
2737
2941
  export function redactDeep(value) {
@@ -2833,7 +3037,7 @@ function unitCarriesCredential(unit) {
2833
3037
  for (const v of armValueClassViews())
2834
3038
  views.add(boundedUnitView(spelling, v.inClass, v.filler));
2835
3039
  views.delete("");
2836
- const hit = [...views].some((view) => runRedaction(view, true, false, false, "bounded-unit", "multi-line").text !== view);
3040
+ const hit = [...views].some((view) => runRedaction(view, true, false, false, "bounded-unit", "multi-line", WHOLE_EXTENT).text !== view);
2837
3041
  return { hit, unreadable: decoded.residue };
2838
3042
  }
2839
3043
  function displaySafePathname(pathname) {
@@ -2863,7 +3067,7 @@ function shapeLawCredentialInQueryOrFragment(canonical) {
2863
3067
  const boundary = canonical.indexOf("?");
2864
3068
  if (boundary < 0)
2865
3069
  return false;
2866
- if (runRedaction(canonical, false, false, false, "free-text", "multi-line").spans.some((sp) => sp.end > boundary))
3070
+ if (runRedaction(canonical, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans.some((sp) => sp.end > boundary))
2867
3071
  return true;
2868
3072
  for (const unit of canonical.slice(boundary + 1).split("&").flatMap((sg) => {
2869
3073
  const eq = sg.indexOf("=");