@sema-agent/server 7.144.0 → 7.146.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.
- package/MIGRATION.md +621 -0
- package/README.md +3 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +233 -54
- package/deploy/facts/CHANGES.json +25 -0
- package/deploy/facts/README.md +57 -0
- package/deploy/facts/lanes.json +54 -0
- package/deploy/facts/lanes.schema.json +57 -0
- package/deploy/facts/sandbox-rbac.json +113 -0
- package/deploy/facts/sandbox-rbac.schema.json +74 -0
- package/deploy/facts/sealed-key.json +9 -0
- package/deploy/facts/sealed-key.schema.json +52 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
- package/deploy/sema-up/chart/templates/server.yaml +4 -0
- package/deploy/sema-up/chart/values.yaml +7 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
- package/dist/adoption/import-limits.d.ts +6 -0
- package/dist/adoption/import-limits.js +7 -0
- package/dist/adoption/import.d.ts +62 -0
- package/dist/adoption/import.js +107 -0
- package/dist/adoption/wire.d.ts +52 -0
- package/dist/adoption/wire.js +9 -0
- package/dist/approval-ask-machine.d.ts +2 -2
- package/dist/approval-card.d.ts +3 -12
- package/dist/approval-card.js +0 -4
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/approval-gate-currency.js +1 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/audit.d.ts +2 -21
- package/dist/audit.js +0 -9
- package/dist/boot/deployment-mods.d.ts +11 -1
- package/dist/boot/deployment-mods.js +15 -2
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/engine-lease.d.ts +193 -21
- package/dist/boot/engine-lease.js +146 -20
- package/dist/boot/execution-env.d.ts +4 -0
- package/dist/boot/execution-env.js +12 -0
- package/dist/boot/exit-boundary.d.ts +50 -0
- package/dist/boot/exit-boundary.js +32 -0
- package/dist/boot/exit-last.d.ts +20 -4
- package/dist/boot/exit-last.js +21 -4
- package/dist/boot/governance-seams.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +29 -17
- package/dist/boot/memory-consolidation.js +26 -15
- package/dist/boot/reapers.d.ts +12 -4
- package/dist/boot/reapers.js +39 -15
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +38 -15
- package/dist/boot/runner-deps.js +1 -1
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +6 -4
- package/dist/boot/shutdown.js +27 -9
- package/dist/boot/stage-01-config.js +1 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +3 -0
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -0
- package/dist/boot/stage-06-runners.js +11 -10
- package/dist/boot/stage-07-capability-layer.d.ts +3 -0
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -0
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +4 -1
- package/dist/boot/stage-10-http-server.d.ts +4 -1
- package/dist/boot/stage-10-http-server.js +4 -3
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +35 -19
- package/dist/boot/workflow-orchestration.d.ts +1 -1
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/capabilities/composite-members.d.ts +3 -1
- package/dist/capabilities/composite-members.js +3 -1
- package/dist/capabilities/scenarios.d.ts +2 -2
- package/dist/capability-facts.d.ts +18 -1
- package/dist/capability-facts.js +25 -7
- package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
- package/dist/config-catalog.js +13 -4
- package/dist/config-d-migration.d.ts +3 -2
- package/dist/config-provider.d.ts +2 -2
- package/dist/config-types.d.ts +43 -4
- package/dist/config.d.ts +6 -1
- package/dist/config.js +67 -11
- package/dist/deleted-session-ids.d.ts +19 -0
- package/dist/deleted-session-ids.js +26 -0
- package/dist/deploy-facts.d.ts +155 -0
- package/dist/deploy-facts.js +94 -0
- package/dist/device-ws-hub.d.ts +48 -3
- package/dist/device-ws-hub.js +48 -2
- package/dist/error-code-family.d.ts +35 -0
- package/dist/error-code-family.js +26 -0
- package/dist/fleet/drain-entry.d.ts +39 -3
- package/dist/fleet/drain-entry.js +6 -0
- package/dist/fleet/fleet-bus.d.ts +15 -4
- package/dist/fleet/fleet-bus.js +17 -3
- package/dist/hooks/branch-transcript.d.ts +1 -1
- package/dist/hooks/branch-transcript.js +1 -0
- package/dist/hooks/classic-stations.d.ts +25 -8
- package/dist/hooks/classic-stations.js +29 -3
- package/dist/hooks/hook-runner.d.ts +33 -16
- package/dist/hooks/hook-runner.js +166 -73
- package/dist/hooks/hook-source.d.ts +14 -2
- package/dist/hooks/hook-source.js +8 -0
- package/dist/http/active-run-conflict.d.ts +1 -1
- package/dist/http/active-run-conflict.js +1 -1
- package/dist/http/admission.js +13 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -71
- package/dist/http/checkpoint-ctx.js +35 -40
- package/dist/http/dispatch.js +18 -2
- package/dist/http/durable-run-leg.d.ts +5 -0
- package/dist/http/durable-run-leg.js +8 -6
- package/dist/http/execution-context.d.ts +4 -0
- package/dist/http/execution-context.js +9 -0
- package/dist/http/health-self-report.d.ts +6 -2
- package/dist/http/health-self-report.js +3 -2
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +2 -15
- package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
- package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
- package/dist/http/principal-gate.d.ts +35 -12
- package/dist/http/principal-gate.js +16 -12
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/request-guards.js +5 -5
- package/dist/http/resume-legs.d.ts +22 -0
- package/dist/http/resume-legs.js +31 -20
- package/dist/http/route-ctx.d.ts +26 -10
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.d.ts +11 -0
- package/dist/http/routes/adoption.js +80 -6
- package/dist/http/routes/agents-roster.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +20 -1
- package/dist/http/routes/approvals-assistant.js +29 -7
- package/dist/http/routes/background.js +1 -0
- package/dist/http/routes/capabilities.js +4 -1
- package/dist/http/routes/diagnostics.d.ts +9 -14
- package/dist/http/routes/diagnostics.js +10 -20
- package/dist/http/routes/engine.d.ts +31 -0
- package/dist/http/routes/engine.js +81 -0
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-consolidation.js +3 -5
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +2 -4
- package/dist/http/routes/memory-policy.js +5 -1
- package/dist/http/routes/observability.js +3 -6
- package/dist/http/routes/rules.d.ts +1 -1
- package/dist/http/routes/rules.js +5 -2
- package/dist/http/routes/runs.js +7 -3
- package/dist/http/routes/session-sync.js +24 -25
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions.js +4 -0
- package/dist/http/routes/tasks.js +35 -14
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/run-concurrency-cap.d.ts +86 -0
- package/dist/http/run-concurrency-cap.js +54 -0
- package/dist/http/run-meta.d.ts +14 -7
- package/dist/http/run-meta.js +5 -2
- package/dist/http/server.d.ts +27 -16
- package/dist/http/server.js +21 -3
- package/dist/http/stored-body-replay.d.ts +9 -6
- package/dist/http/stored-body-replay.js +19 -5
- package/dist/http/submit-ctx.d.ts +12 -0
- package/dist/http/submit-ctx.js +23 -0
- package/dist/http/wire-types.d.ts +8 -3
- package/dist/json-body-limit.d.ts +8 -0
- package/dist/json-body-limit.js +2 -0
- package/dist/leader/diffout.d.ts +1 -1
- package/dist/local-data-root.d.ts +2 -2
- package/dist/memory-operator-faces.d.ts +9 -4
- package/dist/memory-scope-stores.d.ts +9 -7
- package/dist/memory-scope-stores.js +3 -14
- package/dist/memory-scope.d.ts +36 -0
- package/dist/memory-scope.js +32 -1
- package/dist/net-address.d.ts +53 -0
- package/dist/net-address.js +111 -0
- package/dist/observability/fail-open.d.ts +28 -12
- package/dist/observability/fail-open.js +28 -12
- package/dist/orchestration/workflow-notify-journal.js +2 -3
- package/dist/org-delivery-gate.js +1 -1
- package/dist/peer-directory.d.ts +6 -12
- package/dist/peer-directory.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +16 -2
- package/dist/plugins/approval-ask-store-file.js +21 -1
- package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
- package/dist/plugins/approval-ask-store-memory.js +11 -8
- package/dist/plugins/caching-session-store.d.ts +6 -1
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-face.d.ts +426 -0
- package/dist/plugins/checkpoint-store-face.js +152 -0
- package/dist/plugins/file-run-store.d.ts +13 -7
- package/dist/plugins/file-run-store.js +12 -13
- package/dist/plugins/fork-routing-session-store.d.ts +6 -2
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
- package/dist/plugins/local-checkpoint-store.d.ts +16 -5
- package/dist/plugins/local-checkpoint-store.js +25 -6
- package/dist/plugins/local-session-store.d.ts +80 -21
- package/dist/plugins/local-session-store.js +79 -22
- package/dist/plugins/memory-engine-pg.d.ts +29 -6
- package/dist/plugins/memory-engine-pg.js +96 -214
- package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
- package/dist/plugins/memory-engine-sql-apply.js +358 -0
- package/dist/plugins/memory-engine-tidb.d.ts +35 -9
- package/dist/plugins/memory-engine-tidb.js +97 -227
- package/dist/plugins/memory-key-guards.d.ts +1 -1
- package/dist/plugins/memory-origin-law.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +2 -0
- package/dist/plugins/memory-run-store.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +32 -56
- package/dist/plugins/one-time-migrations.js +27 -119
- package/dist/plugins/permission-rule-store-file.d.ts +17 -3
- package/dist/plugins/permission-rule-store-file.js +8 -1
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/run-store-sql.d.ts +4 -0
- package/dist/plugins/run-store-sql.js +5 -2
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/session-fork.d.ts +31 -9
- package/dist/plugins/session-fork.js +3 -2
- package/dist/plugins/session-placement.d.ts +4 -2
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/sql-json-column.d.ts +5 -1
- package/dist/plugins/store-backend.d.ts +11 -8
- package/dist/plugins/store-backend.js +8 -7
- package/dist/plugins/store-contracts.d.ts +1 -11
- package/dist/plugins/store-contracts.js +0 -9
- package/dist/plugins/store-sweep.d.ts +63 -0
- package/dist/plugins/store-sweep.js +48 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/tidb-session-store.d.ts +16 -3
- package/dist/plugins/tidb-session-store.js +8 -4
- package/dist/prompt-screen.d.ts +18 -0
- package/dist/prompt-screen.js +5 -0
- package/dist/rules-consent.d.ts +56 -51
- package/dist/rules-consent.js +86 -14
- package/dist/run-acceptance.d.ts +40 -10
- package/dist/run-acceptance.js +38 -5
- package/dist/run-cancel-context.d.ts +4 -1
- package/dist/run-cancel-context.js +2 -1
- package/dist/runs.js +2 -2
- package/dist/sandbox-egress-policy.js +6 -11
- package/dist/sandbox-rbac-rules.d.ts +88 -0
- package/dist/sandbox-rbac-rules.js +80 -0
- package/dist/sealed-key-layout.d.ts +17 -0
- package/dist/sealed-key-layout.js +4 -0
- package/dist/sealed-key.js +5 -5
- package/dist/security.d.ts +48 -9
- package/dist/security.js +9 -2
- package/dist/session-agent.d.ts +39 -0
- package/dist/session-agent.js +16 -0
- package/dist/session-error-wire.d.ts +4 -4
- package/dist/session-notify-door.d.ts +5 -3
- package/dist/session-resident-background.d.ts +57 -0
- package/dist/session-resident-background.js +20 -0
- package/dist/session-sync-kernel.d.ts +53 -14
- package/dist/session-sync-kernel.js +70 -1
- package/dist/session-sync.d.ts +5 -3
- package/dist/session-workspace.d.ts +1 -0
- package/dist/session-workspace.js +1 -0
- package/dist/sighup-idle.d.ts +9 -7
- package/dist/sighup-idle.js +4 -5
- package/dist/spec-fields.d.ts +16 -0
- package/dist/spec-fields.js +7 -0
- package/dist/stored-request-env-credentials-m38.d.ts +3 -0
- package/dist/stored-request-env-credentials-m38.js +8 -0
- package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
- package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
- package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
- package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
- package/dist/task-settings.d.ts +59 -4
- package/dist/task-settings.js +50 -4
- package/dist/tool-approval.d.ts +2 -2
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +6 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +3 -2
- package/dist/trace/model-segment.js +16 -8
- package/dist/trace/project.js +9 -0
- package/dist/trace/redact.d.ts +79 -4
- package/dist/trace/redact.js +523 -123
- package/dist/trace/self-minted-wire.d.ts +391 -1
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/usage-analytics.d.ts +4 -0
- package/package.json +12 -4
- package/scripts/drain-check-local.mjs +8 -29
- package/scripts/drain-check.mysql.sql +3 -24
- package/scripts/drain-check.pg.sql +3 -24
- package/skills/update-config.md +1 -1
- package/wire-changes/server@7.145.0.json +146 -0
- package/wire-changes/server@7.146.0.json +3221 -0
- package/dist/approval-batch-census.d.ts +0 -27
- package/dist/approval-batch-census.js +0 -27
package/dist/trace/redact.js
CHANGED
|
@@ -73,7 +73,7 @@ function collectSpelledCuts(s) {
|
|
|
73
73
|
let present = 0;
|
|
74
74
|
let i = s.indexOf("\\");
|
|
75
75
|
if (i < 0)
|
|
76
|
-
return { cuts, present };
|
|
76
|
+
return { cuts, present, open: -1 };
|
|
77
77
|
const n = s.length;
|
|
78
78
|
const read = { end: 0 };
|
|
79
79
|
while (i >= 0) {
|
|
@@ -81,6 +81,8 @@ function collectSpelledCuts(s) {
|
|
|
81
81
|
while (body < n && s.charCodeAt(body) === CH_BACKSLASH)
|
|
82
82
|
body += 1;
|
|
83
83
|
const cp = readSpelledCodePoint(s, body, read);
|
|
84
|
+
if (cp === ESCAPE_TRUNCATED)
|
|
85
|
+
return { cuts, present, open: i };
|
|
84
86
|
const cls = cp < 0 ? 0 : isFormatCodePoint(cp) ? SPELLED_FORMAT : cp < 0x10000 && INVISIBLE_UNIT_CLASS[cp] === 1 ? SPELLED_CONTROL : 0;
|
|
85
87
|
if (cls !== 0) {
|
|
86
88
|
cuts.push(i, read.end, cls);
|
|
@@ -89,7 +91,7 @@ function collectSpelledCuts(s) {
|
|
|
89
91
|
}
|
|
90
92
|
i = s.indexOf("\\", body);
|
|
91
93
|
}
|
|
92
|
-
return { cuts, present };
|
|
94
|
+
return { cuts, present, open: -1 };
|
|
93
95
|
}
|
|
94
96
|
function deleteInvisibleCharacters(s, all, mask) {
|
|
95
97
|
const atList = [];
|
|
@@ -127,9 +129,12 @@ function viewIndexInInput(fold, v) {
|
|
|
127
129
|
}
|
|
128
130
|
return lo === 0 ? v : v + fold.cum[lo - 1];
|
|
129
131
|
}
|
|
132
|
+
const ESCAPE_TRUNCATED = -2;
|
|
130
133
|
function readHexRun(s, at, count) {
|
|
131
134
|
let v = 0;
|
|
132
135
|
for (let k = at; k < at + count; k++) {
|
|
136
|
+
if (k >= s.length)
|
|
137
|
+
return ESCAPE_TRUNCATED;
|
|
133
138
|
const d = hexDigitValue(s.charCodeAt(k));
|
|
134
139
|
if (d < 0)
|
|
135
140
|
return -1;
|
|
@@ -138,6 +143,8 @@ function readHexRun(s, at, count) {
|
|
|
138
143
|
return v;
|
|
139
144
|
}
|
|
140
145
|
function readEscapeElement(s, at, out) {
|
|
146
|
+
if (at >= s.length)
|
|
147
|
+
return ESCAPE_TRUNCATED;
|
|
141
148
|
const kind = s.charCodeAt(at);
|
|
142
149
|
if (kind === CH_x) {
|
|
143
150
|
out.end = at + 3;
|
|
@@ -145,6 +152,8 @@ function readEscapeElement(s, at, out) {
|
|
|
145
152
|
}
|
|
146
153
|
if (kind !== CH_u)
|
|
147
154
|
return -1;
|
|
155
|
+
if (at + 1 >= s.length)
|
|
156
|
+
return ESCAPE_TRUNCATED;
|
|
148
157
|
if (s.charCodeAt(at + 1) === CH_LBRACE) {
|
|
149
158
|
let v = 0;
|
|
150
159
|
let k = at + 2;
|
|
@@ -156,6 +165,8 @@ function readEscapeElement(s, at, out) {
|
|
|
156
165
|
if (v > 0x10ffff)
|
|
157
166
|
return -1;
|
|
158
167
|
}
|
|
168
|
+
if (k >= s.length)
|
|
169
|
+
return ESCAPE_TRUNCATED;
|
|
159
170
|
if (k === at + 2 || s.charCodeAt(k) !== CH_RBRACE)
|
|
160
171
|
return -1;
|
|
161
172
|
out.end = k + 1;
|
|
@@ -171,13 +182,20 @@ function readSpelledCodePoint(s, at, out) {
|
|
|
171
182
|
if (v > 0xdbff)
|
|
172
183
|
return -1;
|
|
173
184
|
let k = out.end;
|
|
185
|
+
if (k >= s.length)
|
|
186
|
+
return ESCAPE_TRUNCATED;
|
|
174
187
|
if (s.charCodeAt(k) !== CH_BACKSLASH)
|
|
175
188
|
return -1;
|
|
176
189
|
while (s.charCodeAt(k) === CH_BACKSLASH)
|
|
177
190
|
k += 1;
|
|
178
191
|
const lo = readEscapeElement(s, k, out);
|
|
192
|
+
if (lo === ESCAPE_TRUNCATED)
|
|
193
|
+
return ESCAPE_TRUNCATED;
|
|
179
194
|
return lo >= 0xdc00 && lo <= 0xdfff ? 0x10000 + ((v - 0xd800) << 10) + (lo - 0xdc00) : -1;
|
|
180
195
|
}
|
|
196
|
+
function isEscapeBodyUnit(c) {
|
|
197
|
+
return c === CH_BACKSLASH || c === CH_u || c === CH_x || c === CH_LBRACE || c === CH_RBRACE || hexDigitValue(c) >= 0;
|
|
198
|
+
}
|
|
181
199
|
const FORMAT_CODEPOINT_TABLE = (() => {
|
|
182
200
|
const table = new Uint8Array(0x10000);
|
|
183
201
|
const units = [];
|
|
@@ -463,9 +481,13 @@ let ssh2ScanBytes = 0;
|
|
|
463
481
|
let mappedUnits = 0;
|
|
464
482
|
let coverageVisits = 0;
|
|
465
483
|
let rowVisits = 0;
|
|
484
|
+
let scannedUnits = 0;
|
|
485
|
+
let windowAdvances = 0;
|
|
486
|
+
let windowRefusals = 0;
|
|
487
|
+
let windowEvaluations = 0;
|
|
466
488
|
let readings = 0;
|
|
467
489
|
export function armPoolBudgetForTest() {
|
|
468
|
-
return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, forensicRuns, forensicSkips, urlSegmentRuns, urlSegmentReplays, ssh2ScanBytes, mappedUnits, readings, coverageVisits, rowVisits };
|
|
490
|
+
return { spanCount: poolSpanCount, coveredPositions, headScreenHits, fullYCalls, armCount: poolArmCount, boundaryArmCount: poolBoundaryArmCount, forensicLevels, forensicRuns, forensicSkips, urlSegmentRuns, urlSegmentReplays, ssh2ScanBytes, mappedUnits, readings, coverageVisits, rowVisits, scannedUnits, windowAdvances, windowRefusals, windowEvaluations };
|
|
469
491
|
}
|
|
470
492
|
export function resetArmPoolBudgetForTest() {
|
|
471
493
|
poolSpanCount = 0;
|
|
@@ -482,6 +504,10 @@ export function resetArmPoolBudgetForTest() {
|
|
|
482
504
|
readings = 0;
|
|
483
505
|
coverageVisits = 0;
|
|
484
506
|
rowVisits = 0;
|
|
507
|
+
scannedUnits = 0;
|
|
508
|
+
windowAdvances = 0;
|
|
509
|
+
windowRefusals = 0;
|
|
510
|
+
windowEvaluations = 0;
|
|
485
511
|
}
|
|
486
512
|
const unitBit = (t, c) => (t[c >>> 5] >>> (c & 31)) & 1;
|
|
487
513
|
let codeUnitDomain;
|
|
@@ -1036,7 +1062,7 @@ function undecidedKeywordStart(text, t) {
|
|
|
1036
1062
|
}
|
|
1037
1063
|
const CREDENTIAL_NAME_ANCHORS = {
|
|
1038
1064
|
api_key: "declarative", apikey: "declarative", "api-key": "declarative", key: "structure-only",
|
|
1039
|
-
token: "
|
|
1065
|
+
token: "assignment", access_token: "declarative", refresh_token: "declarative", id_token: "declarative",
|
|
1040
1066
|
auth: "structure-only", authorization: "auth-scheme", "proxy-authorization": "auth-scheme",
|
|
1041
1067
|
password: "declarative", passwd: "any-form", pwd: "any-form",
|
|
1042
1068
|
secret: "declarative", client_secret: "declarative", private_key: "declarative",
|
|
@@ -1079,6 +1105,33 @@ function credentialNameAnchor(name) {
|
|
|
1079
1105
|
return CREDENTIAL_ANCHOR_OF.get(asciiLower(name));
|
|
1080
1106
|
}
|
|
1081
1107
|
const CREDENTIAL_ANCHOR_OF = new Map(Object.entries(CREDENTIAL_NAME_ANCHORS));
|
|
1108
|
+
const BARE_WORD_READINGS = {
|
|
1109
|
+
null: "absent-or-boolean", nil: "absent-or-boolean", none: "absent-or-boolean", undefined: "absent-or-boolean",
|
|
1110
|
+
true: "absent-or-boolean", false: "absent-or-boolean", yes: "absent-or-boolean", no: "absent-or-boolean",
|
|
1111
|
+
str: "type-name", string: "type-name", bytes: "type-name", int: "type-name", integer: "type-name", float: "type-name", number: "type-name",
|
|
1112
|
+
bool: "type-name", boolean: "type-name", any: "type-name", object: "type-name", array: "type-name", secretstr: "type-name",
|
|
1113
|
+
};
|
|
1114
|
+
for (const w of Object.keys(BARE_WORD_READINGS)) {
|
|
1115
|
+
if (!/^[a-z]+$/.test(w))
|
|
1116
|
+
throw new Error(`redact: bare value word ${JSON.stringify(w)} must be lowercase ASCII letters (values are ASCII-lowercased before the lookup)`);
|
|
1117
|
+
}
|
|
1118
|
+
const BARE_WORD_OF = new Map(Object.entries(BARE_WORD_READINGS));
|
|
1119
|
+
const BARE_WORD_MAX = Math.max(...Object.keys(BARE_WORD_READINGS).map((w) => w.length));
|
|
1120
|
+
function bareWordIsValue(reading, bare) {
|
|
1121
|
+
switch (reading) {
|
|
1122
|
+
case "absent-or-boolean":
|
|
1123
|
+
return false;
|
|
1124
|
+
case "type-name":
|
|
1125
|
+
return bare === "declared";
|
|
1126
|
+
default: {
|
|
1127
|
+
const unhandled = reading;
|
|
1128
|
+
throw new Error(`redact: unhandled bare value word reading ${String(unhandled)}`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
export function lineKeyTypeNamesForTest() {
|
|
1133
|
+
return Object.entries(BARE_WORD_READINGS).filter(([, r]) => r === "type-name").map(([w]) => w);
|
|
1134
|
+
}
|
|
1082
1135
|
const STRUCTURAL_SECRET_PATTERNS = [
|
|
1083
1136
|
[/-----BEGIN(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END(?:[^-](?:[^-](?:[^-]|-(?!-))*)?)?PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
|
|
1084
1137
|
[/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
|
|
@@ -3100,8 +3153,6 @@ function isEngineMarkedCandidate(text, start, end, repl) {
|
|
|
3100
3153
|
return end - start === lead.length + REDACTION_MARK.length + trail.length && text.startsWith(lead, start) && text.startsWith(REDACTION_MARK, start + lead.length) && text.startsWith(trail, end - trail.length);
|
|
3101
3154
|
}
|
|
3102
3155
|
const STRONG_AUTH_SCHEME_MAX = Math.max(...[...STRONG_AUTH_SCHEMES].map((w) => w.length));
|
|
3103
|
-
const ABSENT_OR_BOOLEAN_WORDS = new Set(["null", "nil", "none", "undefined", "true", "false", "yes", "no"]);
|
|
3104
|
-
const ABSENT_OR_BOOLEAN_MAX = Math.max(...[...ABSENT_OR_BOOLEAN_WORDS].map((w) => w.length));
|
|
3105
3156
|
const CH_GRAVE = 96, CH_APOS = 39, CH_DOT = 46, CH_COMMA = 44, CH_SEMI = 59, CH_DOLLAR = 36;
|
|
3106
3157
|
const CH_LPAREN = 40, CH_RPAREN = 41, CH_RBRACKET = 93, CH_LBRACE = 123, CH_RBRACE = 125, CH_LT = 60, CH_GT = 62;
|
|
3107
3158
|
const CH_QMARK = 63, CH_AMP = 38, CH_HASH = 35;
|
|
@@ -3128,6 +3179,9 @@ const unitChOf = (unit) => (unit >> 3) - 1;
|
|
|
3128
3179
|
const unitLenOf = (unit) => unit & 7;
|
|
3129
3180
|
function readAnchorUnit(text, i, json) {
|
|
3130
3181
|
anchoredScanUnits += 1;
|
|
3182
|
+
return readTextUnit(text, i, json);
|
|
3183
|
+
}
|
|
3184
|
+
function readTextUnit(text, i, json) {
|
|
3131
3185
|
const c = text.charCodeAt(i);
|
|
3132
3186
|
if (!json)
|
|
3133
3187
|
return packUnit(c, 1);
|
|
@@ -3169,7 +3223,7 @@ function anchorHeadAt(scan, text, op, json) {
|
|
|
3169
3223
|
if (i < 0)
|
|
3170
3224
|
return undefined;
|
|
3171
3225
|
const c = text.charCodeAt(i);
|
|
3172
|
-
if (c
|
|
3226
|
+
if (isInlineSpaceUnit(c)) {
|
|
3173
3227
|
i -= 1;
|
|
3174
3228
|
spacedLeft = true;
|
|
3175
3229
|
anchoredScanUnits += 1;
|
|
@@ -3237,13 +3291,13 @@ function anchorHeadAt(scan, text, op, json) {
|
|
|
3237
3291
|
let listSep = 0;
|
|
3238
3292
|
if (!afterEscape) {
|
|
3239
3293
|
let k = before;
|
|
3240
|
-
while (k >= 0 && (text.charCodeAt(k)
|
|
3294
|
+
while (k >= 0 && isInlineSpaceUnit(text.charCodeAt(k)))
|
|
3241
3295
|
k -= 1;
|
|
3242
3296
|
anchoredScanUnits += before - k;
|
|
3243
3297
|
const left = k >= 0 ? text.charCodeAt(k) : 0;
|
|
3244
3298
|
listSep = left === CH_QMARK || left === CH_AMP || left === CH_HASH ? CH_AMP : left === CH_SEMI ? CH_SEMI : left === CH_COMMA || left === CH_LPAREN || left === CH_LBRACKET || left === CH_LBRACE ? CH_COMMA : 0;
|
|
3245
3299
|
}
|
|
3246
|
-
return { strength, quotedName: quote !== 0, spacedLeft, listSep };
|
|
3300
|
+
return { strength, quotedName: quote !== 0, spacedLeft, listSep, runStart };
|
|
3247
3301
|
}
|
|
3248
3302
|
function quotedValueEnd(text, cs, quote, json, isHead) {
|
|
3249
3303
|
const n = text.length;
|
|
@@ -3356,7 +3410,7 @@ function bareValueEnd(text, s, json, listSep, memo) {
|
|
|
3356
3410
|
}
|
|
3357
3411
|
return e;
|
|
3358
3412
|
}
|
|
3359
|
-
function literalValueAt(text, s, json,
|
|
3413
|
+
function literalValueAt(text, s, json, bare, listSep, isHead, memo) {
|
|
3360
3414
|
const n = text.length;
|
|
3361
3415
|
if (s >= n)
|
|
3362
3416
|
return undefined;
|
|
@@ -3377,8 +3431,21 @@ function literalValueAt(text, s, json, declaredIfBare, listSep, isHead, memo) {
|
|
|
3377
3431
|
}
|
|
3378
3432
|
else if (!isAsciiAlnumCode(first))
|
|
3379
3433
|
return undefined;
|
|
3380
|
-
|
|
3381
|
-
|
|
3434
|
+
let lineKey;
|
|
3435
|
+
switch (bare) {
|
|
3436
|
+
case "never":
|
|
3437
|
+
return undefined;
|
|
3438
|
+
case "declared":
|
|
3439
|
+
lineKey = false;
|
|
3440
|
+
break;
|
|
3441
|
+
case "line-key":
|
|
3442
|
+
lineKey = true;
|
|
3443
|
+
break;
|
|
3444
|
+
default: {
|
|
3445
|
+
const unhandled = bare;
|
|
3446
|
+
throw new Error(`redact: unhandled bare literal form ${String(unhandled)}`);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3382
3449
|
if (isProgramIdentStartCode(text.charCodeAt(vs))) {
|
|
3383
3450
|
let k = vs;
|
|
3384
3451
|
while (k < n && isProgramIdentCode(text.charCodeAt(k)))
|
|
@@ -3391,10 +3458,92 @@ function literalValueAt(text, s, json, declaredIfBare, listSep, isHead, memo) {
|
|
|
3391
3458
|
const e = bareValueEnd(text, vs, json, listSep, memo);
|
|
3392
3459
|
if (e <= vs)
|
|
3393
3460
|
return undefined;
|
|
3394
|
-
if (e - vs <=
|
|
3395
|
-
|
|
3461
|
+
if (e - vs <= BARE_WORD_MAX) {
|
|
3462
|
+
const reading = BARE_WORD_OF.get(asciiLower(text.slice(vs, e)));
|
|
3463
|
+
if (reading !== undefined && !bareWordIsValue(reading, lineKey ? "line-key" : "declared"))
|
|
3464
|
+
return undefined;
|
|
3465
|
+
}
|
|
3466
|
+
if (lineKey) {
|
|
3467
|
+
const rest = lineKeyRestAt(text, e, json, isHead);
|
|
3468
|
+
switch (rest) {
|
|
3469
|
+
case "ends":
|
|
3470
|
+
break;
|
|
3471
|
+
case "goes-on":
|
|
3472
|
+
return undefined;
|
|
3473
|
+
case "undecided":
|
|
3474
|
+
return { start: vs, end: text.length, quoted: false, open: true };
|
|
3475
|
+
default: {
|
|
3476
|
+
const unhandled = rest;
|
|
3477
|
+
throw new Error(`redact: unhandled line-key rest ${String(unhandled)}`);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3396
3481
|
return { start: vs, end: e, quoted: false };
|
|
3397
3482
|
}
|
|
3483
|
+
function lineKeyRestAt(text, e, json, isHead) {
|
|
3484
|
+
const n = text.length;
|
|
3485
|
+
let i = e;
|
|
3486
|
+
while (i < n) {
|
|
3487
|
+
const unit = readAnchorUnit(text, i, json);
|
|
3488
|
+
const ch = unitChOf(unit);
|
|
3489
|
+
if (isInlineSpaceUnit(ch) || (ch >= 0 && INVISIBLE_UNIT_CLASS[ch] === 1)) {
|
|
3490
|
+
i += unitLenOf(unit);
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
return ch === JSON_STRING_END || isLineTerminator(ch) || ch === CH_HASH ? "ends" : "goes-on";
|
|
3494
|
+
}
|
|
3495
|
+
return isHead ? "undecided" : "ends";
|
|
3496
|
+
}
|
|
3497
|
+
function unitEndingAt(text, end, json) {
|
|
3498
|
+
const c = text.charCodeAt(end - 1);
|
|
3499
|
+
anchoredScanUnits += 1;
|
|
3500
|
+
if (!json)
|
|
3501
|
+
return packUnit(c, 1);
|
|
3502
|
+
if (end >= 6 && text.charCodeAt(end - 6) === CH_BACKSLASH && text.charCodeAt(end - 5) === CH_u && !escapedAt(text, end - 6)) {
|
|
3503
|
+
const h0 = hexDigitValue(text.charCodeAt(end - 4));
|
|
3504
|
+
const h1 = hexDigitValue(text.charCodeAt(end - 3));
|
|
3505
|
+
const h2 = hexDigitValue(text.charCodeAt(end - 2));
|
|
3506
|
+
const h3 = hexDigitValue(text.charCodeAt(end - 1));
|
|
3507
|
+
if (h0 >= 0 && h1 >= 0 && h2 >= 0 && h3 >= 0)
|
|
3508
|
+
return packUnit((h0 << 12) | (h1 << 8) | (h2 << 4) | h3, 6);
|
|
3509
|
+
}
|
|
3510
|
+
if (end >= 2 && text.charCodeAt(end - 2) === CH_BACKSLASH && escapedAt(text, end - 1)) {
|
|
3511
|
+
const two = readTextUnit(text, end - 2, true);
|
|
3512
|
+
if (unitLenOf(two) === 2)
|
|
3513
|
+
return two;
|
|
3514
|
+
}
|
|
3515
|
+
return c === CH_QUOTE ? packUnit(JSON_STRING_END, 1) : packUnit(c, 1);
|
|
3516
|
+
}
|
|
3517
|
+
function lineHeadBefore(text, at, json) {
|
|
3518
|
+
let i = at;
|
|
3519
|
+
let blank = false;
|
|
3520
|
+
for (;;) {
|
|
3521
|
+
if (i <= 0)
|
|
3522
|
+
return true;
|
|
3523
|
+
const unit = unitEndingAt(text, i, json);
|
|
3524
|
+
const ch = unitChOf(unit);
|
|
3525
|
+
if (isInlineSpaceUnit(ch)) {
|
|
3526
|
+
i -= unitLenOf(unit);
|
|
3527
|
+
blank = true;
|
|
3528
|
+
continue;
|
|
3529
|
+
}
|
|
3530
|
+
if (ch >= 0 && INVISIBLE_UNIT_CLASS[ch] === 1) {
|
|
3531
|
+
i -= unitLenOf(unit);
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
if (ch === CH_MINUS && blank) {
|
|
3535
|
+
i -= unitLenOf(unit);
|
|
3536
|
+
blank = false;
|
|
3537
|
+
continue;
|
|
3538
|
+
}
|
|
3539
|
+
return isLineTerminator(ch) || ch === JSON_STRING_END;
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
function declarativeBareAt(text, head, spacedRight, json) {
|
|
3543
|
+
if (head.quotedName || (!head.spacedLeft && !spacedRight))
|
|
3544
|
+
return "declared";
|
|
3545
|
+
return spacedRight && lineHeadBefore(text, head.runStart, json) ? "line-key" : "never";
|
|
3546
|
+
}
|
|
3398
3547
|
function schemeValueAt(text, s, json) {
|
|
3399
3548
|
const n = text.length;
|
|
3400
3549
|
let i = s;
|
|
@@ -3478,11 +3627,16 @@ function anchoredValueAt(scan, text, op, json, span, isHead, memo) {
|
|
|
3478
3627
|
case "auth-scheme":
|
|
3479
3628
|
v = schemeValueAt(text, s, json);
|
|
3480
3629
|
break;
|
|
3630
|
+
case "assignment":
|
|
3631
|
+
if (text.charCodeAt(op) !== CH_EQ || head.quotedName || head.spacedLeft || spacedRight)
|
|
3632
|
+
return;
|
|
3633
|
+
v = literalValueAt(text, s, json, "declared", head.listSep, isHead, memo);
|
|
3634
|
+
break;
|
|
3481
3635
|
case "declarative":
|
|
3482
|
-
v = literalValueAt(text, s, json,
|
|
3636
|
+
v = literalValueAt(text, s, json, declarativeBareAt(text, head, spacedRight, json), head.listSep, isHead, memo);
|
|
3483
3637
|
break;
|
|
3484
3638
|
case "any-form":
|
|
3485
|
-
v = literalValueAt(text, s, json,
|
|
3639
|
+
v = literalValueAt(text, s, json, "declared", head.listSep, isHead, memo);
|
|
3486
3640
|
break;
|
|
3487
3641
|
default: {
|
|
3488
3642
|
const unhandled = head.strength;
|
|
@@ -3566,6 +3720,142 @@ function collectAnchoredValueSpans(scan, text, json, span, isHead) {
|
|
|
3566
3720
|
scan.footLo = armLo;
|
|
3567
3721
|
scan.footHi = armHi;
|
|
3568
3722
|
}
|
|
3723
|
+
const isRegexWordCode = (c) => isAsciiAlnumCode(c) || c === CH_UNDERSCORE;
|
|
3724
|
+
const isDiagnosticNameCode = (c) => isRegexWordCode(c) || c === CH_MINUS;
|
|
3725
|
+
const isDiagnosticValueUnit = (ch) => ch !== JSON_STRING_END && !isRegexSpace(ch) && ch !== CH_QUOTE && ch !== CH_APOS && ch !== CH_COMMA && ch !== CH_RBRACE;
|
|
3726
|
+
const isAsciiLetterCode = (ch) => (ch >= 0x41 && ch <= 0x5a) || (ch >= 0x61 && ch <= 0x7a);
|
|
3727
|
+
const isDiagnosticSchemeValueUnit = (ch) => isAsciiAlnumCode(ch) || ch === CH_DOT || ch === CH_UNDERSCORE || ch === CH_TILDE || ch === CH_PLUS || ch === CH_SLASH || ch === CH_EQ || ch === CH_MINUS;
|
|
3728
|
+
function diagnosticNameSlot(tier) {
|
|
3729
|
+
switch (tier) {
|
|
3730
|
+
case "assignment":
|
|
3731
|
+
case "declarative":
|
|
3732
|
+
case "any-form":
|
|
3733
|
+
case "auth-scheme":
|
|
3734
|
+
return true;
|
|
3735
|
+
case "structure-only":
|
|
3736
|
+
return false;
|
|
3737
|
+
default: {
|
|
3738
|
+
const unhandled = tier;
|
|
3739
|
+
throw new Error(`redact: unhandled free-text anchor strength ${String(unhandled)}`);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
const DIAGNOSTIC_SCHEME_RE = new RegExp([...STRONG_AUTH_SCHEMES].sort((a, b) => b.length - a.length).join("|"), "gi");
|
|
3744
|
+
let diagnosticScanUnits = 0;
|
|
3745
|
+
export function diagnosticScanUnitsForTest() {
|
|
3746
|
+
return diagnosticScanUnits;
|
|
3747
|
+
}
|
|
3748
|
+
export function resetDiagnosticScanUnitsForTest() {
|
|
3749
|
+
diagnosticScanUnits = 0;
|
|
3750
|
+
}
|
|
3751
|
+
function collectDiagnosticValueSpans(text, json, span, isHead, deletedAt) {
|
|
3752
|
+
const n = text.length;
|
|
3753
|
+
const readRun = (from, isValue) => {
|
|
3754
|
+
let i = from;
|
|
3755
|
+
while (i < n) {
|
|
3756
|
+
const unit = readTextUnit(text, i, json);
|
|
3757
|
+
diagnosticScanUnits += 1;
|
|
3758
|
+
if (!isValue(unitChOf(unit)))
|
|
3759
|
+
break;
|
|
3760
|
+
i += unitLenOf(unit);
|
|
3761
|
+
}
|
|
3762
|
+
return i;
|
|
3763
|
+
};
|
|
3764
|
+
const skipBlank = (from) => readRun(from, isRegexSpace);
|
|
3765
|
+
let valueEnd = 0;
|
|
3766
|
+
let nextEq = text.indexOf("=");
|
|
3767
|
+
let nextColon = text.indexOf(":");
|
|
3768
|
+
while (nextEq >= 0 || nextColon >= 0) {
|
|
3769
|
+
let op;
|
|
3770
|
+
if (nextColon < 0 || (nextEq >= 0 && nextEq < nextColon)) {
|
|
3771
|
+
op = nextEq;
|
|
3772
|
+
nextEq = text.indexOf("=", op + 1);
|
|
3773
|
+
}
|
|
3774
|
+
else {
|
|
3775
|
+
op = nextColon;
|
|
3776
|
+
nextColon = text.indexOf(":", op + 1);
|
|
3777
|
+
}
|
|
3778
|
+
if (op + 1 < valueEnd)
|
|
3779
|
+
continue;
|
|
3780
|
+
let e = op;
|
|
3781
|
+
while (e > 0 && isRegexSpace(text.charCodeAt(e - 1)))
|
|
3782
|
+
e -= 1;
|
|
3783
|
+
diagnosticScanUnits += op - e;
|
|
3784
|
+
if (e === 0 || !isRegexWordCode(text.charCodeAt(e - 1)))
|
|
3785
|
+
continue;
|
|
3786
|
+
const floor = Math.max(0, e - CREDENTIAL_NAME_MAX - 1);
|
|
3787
|
+
let runStart = e;
|
|
3788
|
+
while (runStart > floor && isDiagnosticNameCode(text.charCodeAt(runStart - 1)))
|
|
3789
|
+
runStart -= 1;
|
|
3790
|
+
diagnosticScanUnits += e - runStart;
|
|
3791
|
+
const runOpen = runStart === floor && floor > 0 && isDiagnosticNameCode(text.charCodeAt(floor - 1));
|
|
3792
|
+
let nameStart = -1;
|
|
3793
|
+
let nameTier;
|
|
3794
|
+
for (let q = runStart; q < e && nameStart < 0; q++) {
|
|
3795
|
+
if (e - q > CREDENTIAL_NAME_MAX || !isRegexWordCode(text.charCodeAt(q)))
|
|
3796
|
+
continue;
|
|
3797
|
+
const bounded = (q === runStart && !runOpen) || text.charCodeAt(q - 1) === CH_MINUS || deletedAt(q) !== "none";
|
|
3798
|
+
const tier = bounded ? credentialNameAnchor(text.slice(q, e)) : undefined;
|
|
3799
|
+
if (tier !== undefined && diagnosticNameSlot(tier)) {
|
|
3800
|
+
nameStart = q;
|
|
3801
|
+
nameTier = tier;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
if (nameStart < 0)
|
|
3805
|
+
continue;
|
|
3806
|
+
let s = skipBlank(op + 1);
|
|
3807
|
+
if (s < n) {
|
|
3808
|
+
const unit = readTextUnit(text, s, json);
|
|
3809
|
+
if (unitChOf(unit) === CH_QUOTE)
|
|
3810
|
+
s += unitLenOf(unit);
|
|
3811
|
+
}
|
|
3812
|
+
if (s >= n) {
|
|
3813
|
+
if (isHead)
|
|
3814
|
+
span(nameStart, n, op, NOT_CLOSED, true);
|
|
3815
|
+
continue;
|
|
3816
|
+
}
|
|
3817
|
+
let end = readRun(s, isDiagnosticValueUnit);
|
|
3818
|
+
if (end > s && nameTier === "auth-scheme" && readRun(s, isAsciiLetterCode) === end) {
|
|
3819
|
+
const k = skipBlank(end);
|
|
3820
|
+
if (k > end && k >= n && isHead) {
|
|
3821
|
+
span(nameStart, n, op, NOT_CLOSED, true);
|
|
3822
|
+
continue;
|
|
3823
|
+
}
|
|
3824
|
+
if (k > end && k < n) {
|
|
3825
|
+
const credEnd = readRun(k, isDiagnosticSchemeValueUnit);
|
|
3826
|
+
if (credEnd > k)
|
|
3827
|
+
end = credEnd;
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
if (end > s) {
|
|
3831
|
+
span(s, end, op, NOT_CLOSED);
|
|
3832
|
+
valueEnd = end;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
let schemeEnd = 0;
|
|
3836
|
+
DIAGNOSTIC_SCHEME_RE.lastIndex = 0;
|
|
3837
|
+
for (let m = DIAGNOSTIC_SCHEME_RE.exec(text); m !== null; m = DIAGNOSTIC_SCHEME_RE.exec(text)) {
|
|
3838
|
+
const at = m.index;
|
|
3839
|
+
const q = at + m[0].length;
|
|
3840
|
+
if (q < schemeEnd)
|
|
3841
|
+
continue;
|
|
3842
|
+
if (at > 0 && isRegexWordCode(text.charCodeAt(at - 1)) && deletedAt(at) === "none")
|
|
3843
|
+
continue;
|
|
3844
|
+
const k = skipBlank(q);
|
|
3845
|
+
if (k === q && deletedAt(q) !== "blank")
|
|
3846
|
+
continue;
|
|
3847
|
+
if (k >= n) {
|
|
3848
|
+
if (isHead)
|
|
3849
|
+
span(at, n, at, NOT_CLOSED, true);
|
|
3850
|
+
continue;
|
|
3851
|
+
}
|
|
3852
|
+
const end = readRun(k, isDiagnosticSchemeValueUnit);
|
|
3853
|
+
if (end > k) {
|
|
3854
|
+
span(k, end, at, NOT_CLOSED);
|
|
3855
|
+
schemeEnd = end;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3569
3859
|
export function structuralScannerForTest(arm) {
|
|
3570
3860
|
const collect = (scan) => (text) => {
|
|
3571
3861
|
const out = [];
|
|
@@ -3695,6 +3985,7 @@ function collectPpkContainerSpans(scan, text, eat, span, isHead) {
|
|
|
3695
3985
|
if (last >= 0 && isOpenHead(last) && !lineHeld(scan, last, text.length, readingKey(fam, last)))
|
|
3696
3986
|
span(last, text.length, last, NOT_CLOSED, true);
|
|
3697
3987
|
}
|
|
3988
|
+
const NO_GAP = (_unit) => false;
|
|
3698
3989
|
const armPool = (() => {
|
|
3699
3990
|
const spanRepl = (token) => {
|
|
3700
3991
|
const body = token.startsWith("$1") ? token.slice(2) : token;
|
|
@@ -3723,6 +4014,7 @@ const armPool = (() => {
|
|
|
3723
4014
|
order,
|
|
3724
4015
|
phase: 1,
|
|
3725
4016
|
lexical: LEXICAL_KEYWORD_ARMS.has(re),
|
|
4017
|
+
diagnosticOnly: false,
|
|
3726
4018
|
collect: (text, _json, _eat, out, isHead) => {
|
|
3727
4019
|
if (boundary === undefined)
|
|
3728
4020
|
collectRegexSpans(arm, text, out);
|
|
@@ -3739,26 +4031,28 @@ const armPool = (() => {
|
|
|
3739
4031
|
headFirst: boundary === undefined ? undefined : headFirst,
|
|
3740
4032
|
family: "prose",
|
|
3741
4033
|
reach: regexDecisionReach(re, LEXICAL_KEYWORD_ARMS.has(re)),
|
|
4034
|
+
prior: { kind: "bounded", gap: keywordTail === undefined ? NO_GAP : (c) => keywordTail.isSeparator(String.fromCharCode(c)) },
|
|
3742
4035
|
};
|
|
3743
4036
|
};
|
|
3744
|
-
const scannerMember = (token, order, arm, reach, family) => {
|
|
4037
|
+
const scannerMember = (token, order, arm, reach, family, prior, rank = ARM_RANK.structural, lexical = false, diagnosticOnly = false) => {
|
|
3745
4038
|
const repl = spanRepl(token);
|
|
3746
|
-
const rank = ARM_RANK.structural;
|
|
3747
4039
|
return {
|
|
3748
4040
|
token,
|
|
3749
4041
|
rank,
|
|
3750
4042
|
order,
|
|
3751
4043
|
phase: 1,
|
|
3752
|
-
lexical
|
|
3753
|
-
|
|
4044
|
+
lexical,
|
|
4045
|
+
diagnosticOnly,
|
|
4046
|
+
collect: (text, json, eat, out, isHead, deletedAt) => arm(text, json, eat, (start, end, anchor, closeAt, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided, anchor, closeAt } : { start, end, repl: r, rank, order, anchor, closeAt }), isHead, deletedAt),
|
|
3754
4047
|
retry: undefined,
|
|
3755
4048
|
headSrc: undefined,
|
|
3756
4049
|
headFirst: undefined,
|
|
3757
4050
|
family,
|
|
3758
4051
|
reach,
|
|
4052
|
+
prior,
|
|
3759
4053
|
};
|
|
3760
4054
|
};
|
|
3761
|
-
const forensicMember = (token, order, arm, reach, family, rank = ARM_RANK.structural, lexical = false) => {
|
|
4055
|
+
const forensicMember = (token, order, arm, reach, family, prior, rank = ARM_RANK.structural, lexical = false) => {
|
|
3762
4056
|
const repl = spanRepl(token);
|
|
3763
4057
|
return {
|
|
3764
4058
|
token,
|
|
@@ -3766,6 +4060,7 @@ const armPool = (() => {
|
|
|
3766
4060
|
order,
|
|
3767
4061
|
phase: 2,
|
|
3768
4062
|
lexical,
|
|
4063
|
+
diagnosticOnly: false,
|
|
3769
4064
|
collect: (scan, text, json, eat, out, isHead) => arm(scan, text, json, eat, (start, end, anchor, closeAt, undecided, r = repl) => out.push(undecided === true ? { start, end, repl: r, rank, order, undecided, anchor, closeAt } : { start, end, repl: r, rank, order, anchor, closeAt }), isHead),
|
|
3770
4065
|
retry: undefined,
|
|
3771
4066
|
headSrc: undefined,
|
|
@@ -3773,32 +4068,34 @@ const armPool = (() => {
|
|
|
3773
4068
|
family,
|
|
3774
4069
|
repl,
|
|
3775
4070
|
reach,
|
|
4071
|
+
prior,
|
|
3776
4072
|
};
|
|
3777
4073
|
};
|
|
3778
4074
|
const structuralScannerMember = (row, order) => {
|
|
3779
4075
|
if (row === PEM_FENCE_PATTERN)
|
|
3780
|
-
return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), PEM_FENCE_OPEN.length, "pem-armor");
|
|
4076
|
+
return forensicMember(row[1], order, (scan, text, _json, _eat, span, isHead) => collectPemFenceSpans(scan, text, span, isHead), PEM_FENCE_OPEN.length, "pem-armor", { kind: "anchored", anchors: [PEM_FENCE_OPEN, PEM_END_OPEN] });
|
|
3781
4077
|
if (row === SSH2_FENCE_PATTERN)
|
|
3782
|
-
return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, json, span, isHead), SSH2_FENCE_OPEN.length, "ssh2-armor");
|
|
4078
|
+
return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectSsh2FenceSpans(scan, text, json, span, isHead), SSH2_FENCE_OPEN.length, "ssh2-armor", { kind: "anchored", anchors: [SSH2_FENCE_OPEN, SSH2_FENCE_CLOSE] });
|
|
3783
4079
|
if (row === URL_CREDENTIAL_PATTERN)
|
|
3784
|
-
return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead), "://".length, "url");
|
|
4080
|
+
return forensicMember(row[1], order, (scan, text, json, _eat, span, isHead) => collectUrlCredentialSpans(scan, text, json, span, isHead), "://".length, "url", { kind: "bounded", gap: NO_GAP });
|
|
3785
4081
|
if (row === JSON_SECRET_FIELD_PATTERN)
|
|
3786
|
-
return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead), JSON_FIELD_BACK, "json-field");
|
|
4082
|
+
return scannerMember(row[1], order, (text, _json, _eat, span, isHead) => collectJsonSecretFieldSpans(text, span, isHead), JSON_FIELD_BACK, "json-field", { kind: "bounded", gap: isRegexSpace });
|
|
3787
4083
|
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)`);
|
|
3788
4084
|
};
|
|
3789
4085
|
const members = [];
|
|
3790
4086
|
for (const row of STRUCTURAL_SECRET_PATTERNS)
|
|
3791
4087
|
members.push(structuralScannerMember(row, members.length));
|
|
3792
|
-
members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead), PPK_FILE_HEAD.length, "putty-ppk"));
|
|
3793
|
-
members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectPemFragmentSpans(scan, text, json, span, isHead), PEM_FENCE_OPEN.length, "pem-armor"));
|
|
3794
|
-
members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead), PPK_LINES_FIELD.length, "putty-ppk"));
|
|
3795
|
-
members.push(forensicMember(ANCHORED_VALUE_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectAnchoredValueSpans(scan, text, json, span, isHead), Math.max(BEARER_VALUE_MIN,
|
|
4088
|
+
members.push(forensicMember(PPK_CONTAINER_TOKEN, members.length, (scan, text, _json, eat, span, isHead) => collectPpkContainerSpans(scan, text, eat, span, isHead), PPK_FILE_HEAD.length, "putty-ppk", { kind: "anchored", anchors: [PPK_FILE_HEAD] }));
|
|
4089
|
+
members.push(forensicMember(PEM_FRAGMENT_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectPemFragmentSpans(scan, text, json, span, isHead), PEM_FENCE_OPEN.length, "pem-armor", { kind: "anchored", anchors: [PEM_FENCE_OPEN, PEM_END_OPEN] }));
|
|
4090
|
+
members.push(forensicMember(HEADLESS_PPK_TOKEN, members.length, (scan, text, _json, _eat, span, isHead) => collectHeadlessPpkSpans(scan, text, span, isHead), PPK_LINES_FIELD.length, "putty-ppk", { kind: "anchored", anchors: [PPK_LINES_FIELD] }));
|
|
4091
|
+
members.push(forensicMember(ANCHORED_VALUE_TOKEN, members.length, (scan, text, json, _eat, span, isHead) => collectAnchoredValueSpans(scan, text, json, span, isHead), Math.max(BEARER_VALUE_MIN, BARE_WORD_MAX + 1, STRONG_AUTH_SCHEME_MAX + 1), "anchor", { kind: "bounded", gap: (c) => isInlineSpaceUnit(c) || isSchemeDelimiterCode(c) }, ARM_RANK.keyword, true));
|
|
3796
4092
|
for (const row of KEYWORD_SECRET_PATTERNS)
|
|
3797
4093
|
members.push(regexMember(row, ARM_RANK.keyword, members.length));
|
|
3798
4094
|
for (const row of SHAPE_SECRET_PATTERNS)
|
|
3799
4095
|
members.push(regexMember(row, ARM_RANK.shape, members.length));
|
|
3800
4096
|
for (const row of FRAGMENT_SECRET_PATTERNS)
|
|
3801
4097
|
members.push(regexMember(row, ARM_RANK.fragment, members.length));
|
|
4098
|
+
members.push(scannerMember(ANCHORED_VALUE_TOKEN, members.length, (text, json, _eat, span, isHead, deletedAt) => collectDiagnosticValueSpans(text, json, span, isHead, deletedAt), Math.max(CREDENTIAL_NAME_MAX, STRONG_AUTH_SCHEME_MAX) + 2, "prose", { kind: "bounded", gap: isRegexSpace }, ARM_RANK.keyword, true, true));
|
|
3802
4099
|
for (const [re] of KEYWORD_SECRET_PATTERNS) {
|
|
3803
4100
|
if (!LEXICAL_KEYWORD_ARMS.has(re))
|
|
3804
4101
|
throw new Error(`redact: keyword arm /${re.source}/ is not registered as lexical — build it with keywordArm() so the bounded-unit scope can exclude it`);
|
|
@@ -3810,8 +4107,9 @@ const armPool = (() => {
|
|
|
3810
4107
|
const laneOf = (scope) => {
|
|
3811
4108
|
const inScope = (m) => {
|
|
3812
4109
|
switch (scope) {
|
|
3813
|
-
case "free-text": return
|
|
3814
|
-
case "bounded-unit": return !m.lexical;
|
|
4110
|
+
case "free-text": return !m.diagnosticOnly;
|
|
4111
|
+
case "bounded-unit": return !m.lexical && !m.diagnosticOnly;
|
|
4112
|
+
case "diagnostic": return true;
|
|
3815
4113
|
}
|
|
3816
4114
|
};
|
|
3817
4115
|
return {
|
|
@@ -3823,13 +4121,14 @@ const armPool = (() => {
|
|
|
3823
4121
|
const LANES = {
|
|
3824
4122
|
"free-text": laneOf("free-text"),
|
|
3825
4123
|
"bounded-unit": laneOf("bounded-unit"),
|
|
4124
|
+
diagnostic: laneOf("diagnostic"),
|
|
3826
4125
|
};
|
|
3827
4126
|
const headScreen = new RegExp(`(?:${retryMembers.map((m) => m.headSrc).join("|")})`, "y");
|
|
3828
4127
|
const headFirstUnit = unionMembers(retryMembers.map((m) => m.headFirst)) ?? new Uint32Array(0x10000 >>> 5).fill(0xffffffff);
|
|
3829
4128
|
poolArmCount = members.length;
|
|
3830
4129
|
poolBoundaryArmCount = retryMembers.length;
|
|
3831
4130
|
const reaches = members.map((m) => ({ token: m.token, order: m.order, reach: m.reach }));
|
|
3832
|
-
const run = (text, wantCount, json, eat, scope, reach, engineMarked) => {
|
|
4131
|
+
const run = (text, wantCount, json, eat, scope, reach, engineMarked, deletedAt) => {
|
|
3833
4132
|
readings += 1;
|
|
3834
4133
|
const isHead = reach !== undefined;
|
|
3835
4134
|
const lane = LANES[scope];
|
|
@@ -3884,7 +4183,7 @@ const armPool = (() => {
|
|
|
3884
4183
|
}
|
|
3885
4184
|
};
|
|
3886
4185
|
for (const m of lane.phase1)
|
|
3887
|
-
m.collect(text, json, eat, spans, isHead);
|
|
4186
|
+
m.collect(text, json, eat, spans, isHead, deletedAt);
|
|
3888
4187
|
closeCoveragePositions(0);
|
|
3889
4188
|
if (lane.phase2.length > 0) {
|
|
3890
4189
|
const scan = buildForensicScan(tokenUnits(text), buildEvidenceRows(text), lane.phase2.length);
|
|
@@ -3963,14 +4262,14 @@ const armPool = (() => {
|
|
|
3963
4262
|
}
|
|
3964
4263
|
return { hits: [...byArmedToken.values()].map(({ token, arm, count }) => ({ label: labelOfToken(token), count, arm })), kept: stamped };
|
|
3965
4264
|
};
|
|
3966
|
-
return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)) });
|
|
4265
|
+
return Object.assign(run, { reaches, pendingTailUnits: Math.max(...reaches.map((r) => r.reach)), priors: members.map((m) => m.prior) });
|
|
3967
4266
|
})();
|
|
3968
4267
|
let onRedaction;
|
|
3969
4268
|
export function setRedactionObserver(fn) {
|
|
3970
4269
|
onRedaction = fn;
|
|
3971
4270
|
}
|
|
3972
|
-
function applyRules(text, wantCount, json, eat, scope, reach, engineMarked) {
|
|
3973
|
-
return armPool(text, wantCount, json, eat, scope, reach, engineMarked);
|
|
4271
|
+
function applyRules(text, wantCount, json, eat, scope, reach, engineMarked, deletedAt) {
|
|
4272
|
+
return armPool(text, wantCount, json, eat, scope, reach, engineMarked, deletedAt);
|
|
3974
4273
|
}
|
|
3975
4274
|
function reportHits(hits) {
|
|
3976
4275
|
if (onRedaction === undefined)
|
|
@@ -3996,70 +4295,6 @@ function invisibleClassesOf(layout) {
|
|
|
3996
4295
|
}
|
|
3997
4296
|
}
|
|
3998
4297
|
}
|
|
3999
|
-
function trailingPartialEscapeStart(s) {
|
|
4000
|
-
const n = s.length;
|
|
4001
|
-
const runStart = (end) => {
|
|
4002
|
-
let k = end;
|
|
4003
|
-
while (k > 0 && s.charCodeAt(k - 1) === CH_BACKSLASH)
|
|
4004
|
-
k -= 1;
|
|
4005
|
-
return k;
|
|
4006
|
-
};
|
|
4007
|
-
const highSurrogateEscapeStartBefore = (end) => {
|
|
4008
|
-
let j = end;
|
|
4009
|
-
let value = -1;
|
|
4010
|
-
let bodyStart = -1;
|
|
4011
|
-
if (j >= 1 && s.charCodeAt(j - 1) === CH_RBRACE) {
|
|
4012
|
-
let k = j - 1;
|
|
4013
|
-
while (k > 0 && hexDigitValue(s.charCodeAt(k - 1)) >= 0)
|
|
4014
|
-
k -= 1;
|
|
4015
|
-
if (k >= 2 && s.charCodeAt(k - 1) === CH_LBRACE && s.charCodeAt(k - 2) === CH_u && j - 1 - k >= 1) {
|
|
4016
|
-
value = readHexRunValue(s, k, j - 1);
|
|
4017
|
-
bodyStart = k - 2;
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
|
-
else if (j >= 5 && s.charCodeAt(j - 5) === CH_u) {
|
|
4021
|
-
value = readHexRun(s, j - 4, 4);
|
|
4022
|
-
bodyStart = j - 5;
|
|
4023
|
-
}
|
|
4024
|
-
if (value < 0xd800 || value > 0xdbff)
|
|
4025
|
-
return -1;
|
|
4026
|
-
const k = runStart(bodyStart);
|
|
4027
|
-
return k < bodyStart ? k : -1;
|
|
4028
|
-
};
|
|
4029
|
-
let j = n;
|
|
4030
|
-
while (j > 0 && hexDigitValue(s.charCodeAt(j - 1)) >= 0)
|
|
4031
|
-
j -= 1;
|
|
4032
|
-
const hexRun = n - j;
|
|
4033
|
-
let element = -1;
|
|
4034
|
-
if (j >= 2 && s.charCodeAt(j - 1) === CH_LBRACE && s.charCodeAt(j - 2) === CH_u)
|
|
4035
|
-
element = j - 2;
|
|
4036
|
-
else if (j >= 1 && s.charCodeAt(j - 1) === CH_u && hexRun <= 3)
|
|
4037
|
-
element = j - 1;
|
|
4038
|
-
else if (j >= 1 && s.charCodeAt(j - 1) === CH_x && hexRun <= 1)
|
|
4039
|
-
element = j - 1;
|
|
4040
|
-
else if (hexRun === 0 && n >= 1 && s.charCodeAt(n - 1) === CH_BACKSLASH)
|
|
4041
|
-
element = n;
|
|
4042
|
-
if (element >= 0) {
|
|
4043
|
-
const k = runStart(element);
|
|
4044
|
-
if (k >= element)
|
|
4045
|
-
return -1;
|
|
4046
|
-
const high = highSurrogateEscapeStartBefore(k);
|
|
4047
|
-
return high >= 0 ? high : k;
|
|
4048
|
-
}
|
|
4049
|
-
return highSurrogateEscapeStartBefore(n);
|
|
4050
|
-
}
|
|
4051
|
-
function readHexRunValue(s, from, to) {
|
|
4052
|
-
let v = 0;
|
|
4053
|
-
for (let k = from; k < to; k++) {
|
|
4054
|
-
const d = hexDigitValue(s.charCodeAt(k));
|
|
4055
|
-
if (d < 0)
|
|
4056
|
-
return -1;
|
|
4057
|
-
v = v * 16 + d;
|
|
4058
|
-
if (v > 0x10ffff)
|
|
4059
|
-
return -1;
|
|
4060
|
-
}
|
|
4061
|
-
return v;
|
|
4062
|
-
}
|
|
4063
4298
|
function shortestReading(text, layout) {
|
|
4064
4299
|
const r = readShortest(text, layout);
|
|
4065
4300
|
return { visible: r.visible, partial: r.partial };
|
|
@@ -4068,8 +4303,8 @@ function readShortest(text, layout) {
|
|
|
4068
4303
|
const raw = collectRawControlCuts(text, invisibleClassesOf(layout));
|
|
4069
4304
|
const rfold = deleteInvisibleCharacters(text, raw, raw.present);
|
|
4070
4305
|
const normalized = normalizeForRedaction(rfold.text);
|
|
4071
|
-
const open = trailingPartialEscapeStart(normalized);
|
|
4072
4306
|
const spelled = collectSpelledCuts(normalized);
|
|
4307
|
+
const open = spelled.open;
|
|
4073
4308
|
const sfold = deleteInvisibleCharacters(normalized, spelled, spelled.present);
|
|
4074
4309
|
const layers = [{ fold: sfold }, { input: rfold.text, view: normalized }, { fold: rfold }];
|
|
4075
4310
|
return open < 0 ? { visible: sfold.text.length, partial: false, layers } : { visible: sfold.text.length - (normalized.length - open), partial: true, layers };
|
|
@@ -4095,6 +4330,7 @@ export function readingHeadEnd(text, layout, units, from = 0) {
|
|
|
4095
4330
|
return text.length;
|
|
4096
4331
|
}
|
|
4097
4332
|
function runRedaction(text, observe, json, wantHits, scope, layout, extent, observeFrom = 0) {
|
|
4333
|
+
scannedUnits += text.length;
|
|
4098
4334
|
const wantCount = wantHits || (onRedaction !== undefined && observe);
|
|
4099
4335
|
const eat = observe ? { from: -1, len: 0, capFrom: -1, capLen: 0 } : undefined;
|
|
4100
4336
|
let spans = [];
|
|
@@ -4105,7 +4341,19 @@ function runRedaction(text, observe, json, wantHits, scope, layout, extent, obse
|
|
|
4105
4341
|
const toOriginal = createViewToOriginal(layers);
|
|
4106
4342
|
const reach = extent.kind === "head" ? (viewStart) => toOriginal.start(viewStart) < extent.emitEnd : undefined;
|
|
4107
4343
|
const engineMarked = (viewStart, viewEnd, repl) => isEngineMarkedCandidate(text, toOriginal.start(viewStart), toOriginal.start(viewEnd - 1) + 1, repl);
|
|
4108
|
-
const
|
|
4344
|
+
const deletedAt = (viewIndex) => {
|
|
4345
|
+
if (viewIndex <= 0 || viewIndex > view.length)
|
|
4346
|
+
return "none";
|
|
4347
|
+
const lo = toOriginal.start(viewIndex - 1) + 1;
|
|
4348
|
+
const hi = viewIndex === view.length ? text.length : toOriginal.start(viewIndex);
|
|
4349
|
+
if (hi <= lo)
|
|
4350
|
+
return "none";
|
|
4351
|
+
for (let k = lo; k < hi; k++)
|
|
4352
|
+
if (isRegexSpace(text.charCodeAt(k)))
|
|
4353
|
+
return "blank";
|
|
4354
|
+
return "invisible";
|
|
4355
|
+
};
|
|
4356
|
+
const r = applyRules(view, wantCount, json, eat, scope, reach, engineMarked, deletedAt);
|
|
4109
4357
|
forgetEscapedSeparatorTails();
|
|
4110
4358
|
readsSoFar += 1;
|
|
4111
4359
|
if (readsSoFar === 1) {
|
|
@@ -4187,7 +4435,7 @@ export function redactSecretsWithArms(text, opts) {
|
|
|
4187
4435
|
return { text: r.text, arms: new Set(r.hits.map((h) => h.arm)) };
|
|
4188
4436
|
}
|
|
4189
4437
|
export function redactionSpansForTest(text, opts) {
|
|
4190
|
-
const r = runRedaction(text, false, opts?.json === true, false, "free-text", "multi-line", WHOLE_EXTENT);
|
|
4438
|
+
const r = runRedaction(text, false, opts?.json === true, false, opts?.scope ?? "free-text", "multi-line", WHOLE_EXTENT);
|
|
4191
4439
|
return { text: r.text, spans: r.spans.map((sp) => ({ start: sp.start, end: sp.end })) };
|
|
4192
4440
|
}
|
|
4193
4441
|
export function proposeRedactionSpans(text, scope, layout, extent) {
|
|
@@ -4225,67 +4473,219 @@ function renderRange(text, spans, from, until) {
|
|
|
4225
4473
|
}
|
|
4226
4474
|
const STREAM_RESCAN_MIN = 64;
|
|
4227
4475
|
const streamRescanStep = (n) => Math.max(STREAM_RESCAN_MIN, n >>> 5);
|
|
4476
|
+
const STREAM_WINDOW_HORIZON = 2;
|
|
4477
|
+
const STREAM_WINDOW_MIN = 2048;
|
|
4478
|
+
function deriveStreamWindowAlphabets(priors) {
|
|
4479
|
+
const blank = new Uint8Array(0x10000);
|
|
4480
|
+
const gaps = new Set();
|
|
4481
|
+
const containerDelimiters = [];
|
|
4482
|
+
for (const prior of priors) {
|
|
4483
|
+
switch (prior.kind) {
|
|
4484
|
+
case "bounded":
|
|
4485
|
+
if (prior.gap !== NO_GAP)
|
|
4486
|
+
gaps.add(prior.gap);
|
|
4487
|
+
break;
|
|
4488
|
+
case "anchored":
|
|
4489
|
+
if (prior.anchors.length === 0)
|
|
4490
|
+
throw new Error("redact: an anchored arm declares no anchor literal — the stream window cannot tell where its reading of prior text starts");
|
|
4491
|
+
for (const anchor of prior.anchors) {
|
|
4492
|
+
if (anchor.length === 0)
|
|
4493
|
+
throw new Error("redact: an anchored arm declares an empty anchor literal");
|
|
4494
|
+
for (let i = 0; i < anchor.length; i++) {
|
|
4495
|
+
const c = anchor.charCodeAt(i);
|
|
4496
|
+
if (INVISIBLE_UNIT_CLASS[c] !== 0 || FORMAT_CODEPOINT_TABLE[c] === 1 || (c & 0xf800) === 0xd800 || c === CH_BACKSLASH) {
|
|
4497
|
+
throw new Error(`redact: anchor literal ${JSON.stringify(anchor)} holds a unit some reading deletes or reads as an escape (index ${i}) — its presence is not monotone under deletion, so the stream window's delimiter check on the most-deleted reading would be incomplete`);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
if (isEscapeBodyUnit(anchor.charCodeAt(0))) {
|
|
4501
|
+
throw new Error(`redact: anchor literal ${JSON.stringify(anchor)} starts with a unit an escape spelling's body can read — an escape begun just before it deletes that unit in the most-deleted reading, so its presence is not monotone under deletion and the stream window's delimiter check would be incomplete`);
|
|
4502
|
+
}
|
|
4503
|
+
if (!containerDelimiters.includes(anchor))
|
|
4504
|
+
containerDelimiters.push(anchor);
|
|
4505
|
+
}
|
|
4506
|
+
break;
|
|
4507
|
+
default: {
|
|
4508
|
+
const unhandled = prior;
|
|
4509
|
+
throw new Error(`redact: unhandled prior reading ${JSON.stringify(unhandled)}`);
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
for (const gap of gaps)
|
|
4514
|
+
for (let u = 0; u < 0x10000; u++)
|
|
4515
|
+
if (gap(u))
|
|
4516
|
+
blank[u] = 1;
|
|
4517
|
+
return { blank, containerDelimiters };
|
|
4518
|
+
}
|
|
4519
|
+
const STREAM_WINDOW_ALPHABETS = deriveStreamWindowAlphabets(armPool.priors);
|
|
4520
|
+
export function streamWindowAlphabetsForTest(extra = []) {
|
|
4521
|
+
const a = extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]);
|
|
4522
|
+
return { isBlank: (unit) => a.blank[unit] === 1, containerDelimiters: a.containerDelimiters };
|
|
4523
|
+
}
|
|
4524
|
+
export function escapeGrammarForTest() {
|
|
4525
|
+
return {
|
|
4526
|
+
read: (s) => {
|
|
4527
|
+
let body = 0;
|
|
4528
|
+
while (body < s.length && s.charCodeAt(body) === CH_BACKSLASH)
|
|
4529
|
+
body += 1;
|
|
4530
|
+
const out = { end: -1 };
|
|
4531
|
+
const value = readSpelledCodePoint(s, body, out);
|
|
4532
|
+
return { value, end: value >= 0 ? out.end : -1 };
|
|
4533
|
+
},
|
|
4534
|
+
isBodyUnit: isEscapeBodyUnit,
|
|
4535
|
+
};
|
|
4536
|
+
}
|
|
4537
|
+
function windowAdvancePoint(text, units, alphabets = STREAM_WINDOW_ALPHABETS) {
|
|
4538
|
+
const r = readShortest(text, "multi-line");
|
|
4539
|
+
if (r.partial)
|
|
4540
|
+
return -1;
|
|
4541
|
+
const view = r.layers[0].fold.text;
|
|
4542
|
+
let k = r.visible;
|
|
4543
|
+
let n = 0;
|
|
4544
|
+
while (k > 0 && n < units) {
|
|
4545
|
+
k -= 1;
|
|
4546
|
+
if (alphabets.blank[view.charCodeAt(k)] !== 1)
|
|
4547
|
+
n += 1;
|
|
4548
|
+
}
|
|
4549
|
+
if (n < units)
|
|
4550
|
+
return 0;
|
|
4551
|
+
for (const d of alphabets.containerDelimiters) {
|
|
4552
|
+
const at = view.indexOf(d);
|
|
4553
|
+
if (at >= 0 && at < k)
|
|
4554
|
+
k = at;
|
|
4555
|
+
}
|
|
4556
|
+
const at = createViewToOriginal(r.layers).start(k);
|
|
4557
|
+
return at <= 0 ? 0 : text.lastIndexOf("\n", at - 1) + 1;
|
|
4558
|
+
}
|
|
4559
|
+
function windowIndependentAt(text, q) {
|
|
4560
|
+
const full = runRedaction(text, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: text.length }).spans;
|
|
4561
|
+
if (full.some((s) => s.start < q && s.end > q))
|
|
4562
|
+
return false;
|
|
4563
|
+
const after = full.filter((s) => s.start >= q);
|
|
4564
|
+
const tail = text.slice(q);
|
|
4565
|
+
const alone = runRedaction(tail, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd: tail.length }).spans;
|
|
4566
|
+
if (after.length !== alone.length)
|
|
4567
|
+
return false;
|
|
4568
|
+
return after.every((a, i) => a.start - q === alone[i].start && a.end - q === alone[i].end && a.repl === alone[i].repl);
|
|
4569
|
+
}
|
|
4570
|
+
export function windowAdvancePointForTest(text, units, extra = []) {
|
|
4571
|
+
return windowAdvancePoint(text, units, extra.length === 0 ? STREAM_WINDOW_ALPHABETS : deriveStreamWindowAlphabets([...armPool.priors, ...extra]));
|
|
4572
|
+
}
|
|
4573
|
+
export function windowIndependentAtForTest(text, q) {
|
|
4574
|
+
return windowIndependentAt(text, q);
|
|
4575
|
+
}
|
|
4228
4576
|
export function createRedactionStream() {
|
|
4229
4577
|
return openRedactionStream(PENDING_TAIL_UNITS);
|
|
4230
4578
|
}
|
|
4579
|
+
export function createRedactionStreamWithoutWindowForTest() {
|
|
4580
|
+
return openRedactionStream(PENDING_TAIL_UNITS, false);
|
|
4581
|
+
}
|
|
4582
|
+
export function createRedactionStreamWithCertifierForTest(certify) {
|
|
4583
|
+
return openRedactionStream(PENDING_TAIL_UNITS, true, certify);
|
|
4584
|
+
}
|
|
4231
4585
|
export function createRedactionStreamWithTailForTest(units) {
|
|
4232
4586
|
return openRedactionStream(units);
|
|
4233
4587
|
}
|
|
4234
|
-
function openRedactionStream(pendingTail) {
|
|
4235
|
-
let
|
|
4588
|
+
function openRedactionStream(pendingTail, windowed = true, certify = windowIndependentAt) {
|
|
4589
|
+
let past = [];
|
|
4590
|
+
let total = 0;
|
|
4591
|
+
let win = "";
|
|
4592
|
+
let base = 0;
|
|
4593
|
+
let blockFrom = 0;
|
|
4236
4594
|
let released = 0;
|
|
4237
4595
|
let deliveredFrom = 0;
|
|
4238
4596
|
let sent = "";
|
|
4239
4597
|
let scannedAt = 0;
|
|
4598
|
+
let evaluatedAt = -1;
|
|
4599
|
+
let retryAt = 0;
|
|
4600
|
+
const whole = () => (past.length === 0 ? win : past.join("") + win);
|
|
4601
|
+
const advanceWindow = () => {
|
|
4602
|
+
if (!windowed || win.length < Math.max(STREAM_WINDOW_MIN, retryAt) || released < total || total === evaluatedAt)
|
|
4603
|
+
return;
|
|
4604
|
+
evaluatedAt = total;
|
|
4605
|
+
retryAt = win.length + (win.length >> 1);
|
|
4606
|
+
windowEvaluations += 1;
|
|
4607
|
+
const q = windowAdvancePoint(win, STREAM_WINDOW_HORIZON * pendingTail);
|
|
4608
|
+
if (q < 0) {
|
|
4609
|
+
retryAt = 0;
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4612
|
+
if (q === 0 || q * 2 < win.length)
|
|
4613
|
+
return;
|
|
4614
|
+
if (!certify(win, q)) {
|
|
4615
|
+
windowRefusals += 1;
|
|
4616
|
+
return;
|
|
4617
|
+
}
|
|
4618
|
+
past.push(win.slice(0, q));
|
|
4619
|
+
win = win.slice(q);
|
|
4620
|
+
base += q;
|
|
4621
|
+
retryAt = 0;
|
|
4622
|
+
windowAdvances += 1;
|
|
4623
|
+
};
|
|
4240
4624
|
return {
|
|
4241
4625
|
feed(chunk) {
|
|
4242
|
-
|
|
4626
|
+
win += chunk;
|
|
4627
|
+
total += chunk.length;
|
|
4243
4628
|
},
|
|
4244
4629
|
takeSettled() {
|
|
4245
|
-
if (
|
|
4630
|
+
if (total - released <= pendingTail)
|
|
4246
4631
|
return "";
|
|
4247
|
-
if (
|
|
4632
|
+
if (total - scannedAt < streamRescanStep(total))
|
|
4248
4633
|
return "";
|
|
4249
|
-
scannedAt =
|
|
4250
|
-
const emitEnd = pendingTailStart(
|
|
4251
|
-
|
|
4634
|
+
scannedAt = total;
|
|
4635
|
+
const emitEnd = pendingTailStart(win, pendingTail);
|
|
4636
|
+
const from = released - base;
|
|
4637
|
+
if (emitEnd <= from)
|
|
4252
4638
|
return "";
|
|
4253
|
-
const spans = runRedaction(
|
|
4639
|
+
const spans = runRedaction(win, false, false, false, "free-text", "multi-line", { kind: "head", emitEnd }).spans;
|
|
4254
4640
|
let cut = emitEnd;
|
|
4255
4641
|
for (const s of spans)
|
|
4256
4642
|
if (s.start < cut && s.end > cut)
|
|
4257
4643
|
cut = s.start;
|
|
4258
|
-
if (cut > 0 && cut <
|
|
4644
|
+
if (cut > 0 && cut < win.length && (win.charCodeAt(cut - 1) & 0xfc00) === 0xd800 && (win.charCodeAt(cut) & 0xfc00) === 0xdc00)
|
|
4259
4645
|
cut -= 1;
|
|
4260
|
-
if (cut <=
|
|
4646
|
+
if (cut <= from)
|
|
4261
4647
|
return "";
|
|
4262
|
-
const out = renderRange(
|
|
4263
|
-
released = cut;
|
|
4648
|
+
const out = renderRange(win, spans, from, cut);
|
|
4649
|
+
released = base + cut;
|
|
4264
4650
|
sent += out;
|
|
4265
4651
|
return out;
|
|
4266
4652
|
},
|
|
4267
4653
|
takeAll() {
|
|
4268
|
-
if (released ===
|
|
4654
|
+
if (released === total)
|
|
4269
4655
|
return "";
|
|
4270
|
-
const spans = runRedaction(
|
|
4271
|
-
const out = renderRange(
|
|
4272
|
-
released = scannedAt =
|
|
4656
|
+
const spans = runRedaction(win, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
|
|
4657
|
+
const out = renderRange(win, spans, released - base, win.length);
|
|
4658
|
+
released = scannedAt = total;
|
|
4273
4659
|
sent += out;
|
|
4274
4660
|
return out;
|
|
4275
4661
|
},
|
|
4276
4662
|
flush() {
|
|
4277
|
-
if (deliveredFrom ===
|
|
4663
|
+
if (deliveredFrom === total)
|
|
4278
4664
|
return { rest: "", diverged: false };
|
|
4665
|
+
const buf = whole();
|
|
4279
4666
|
const spans = runRedaction(buf, false, false, false, "free-text", "multi-line", WHOLE_EXTENT).spans;
|
|
4280
4667
|
const split = released > deliveredFrom && spans.some((s) => s.start < released && s.end > released);
|
|
4281
4668
|
return { rest: renderRange(buf, spans, released, buf.length), diverged: split || renderRange(buf, spans, deliveredFrom, released) !== sent };
|
|
4282
4669
|
},
|
|
4283
4670
|
skip() {
|
|
4284
|
-
released = deliveredFrom = scannedAt =
|
|
4671
|
+
released = deliveredFrom = scannedAt = total;
|
|
4285
4672
|
sent = "";
|
|
4286
4673
|
},
|
|
4674
|
+
endBlock() {
|
|
4675
|
+
blockFrom = total;
|
|
4676
|
+
advanceWindow();
|
|
4677
|
+
},
|
|
4678
|
+
blockContext() {
|
|
4679
|
+
return win.slice(0, blockFrom - base);
|
|
4680
|
+
},
|
|
4681
|
+
blockBytes() {
|
|
4682
|
+
return win.slice(blockFrom - base);
|
|
4683
|
+
},
|
|
4287
4684
|
get fed() {
|
|
4288
|
-
return
|
|
4685
|
+
return whole();
|
|
4686
|
+
},
|
|
4687
|
+
get length() {
|
|
4688
|
+
return total;
|
|
4289
4689
|
},
|
|
4290
4690
|
};
|
|
4291
4691
|
}
|