agent-sanitizer 2.45.9 → 2.47.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/README.md +12 -11
- package/claude-hooks/lib/hook-io.mjs +12 -15
- package/claude-hooks/lib/invisible-alert.mjs +11 -8
- package/package.json +5 -1
- package/src/index.mjs +2 -0
- package/src/layer1.mjs +59 -5
- package/src/output.mjs +1 -16
- package/src/rehydrate.mjs +2 -2
- package/types/claude-hooks/lib/hook-io.d.mts +11 -7
- package/types/index.d.mts +1 -1
- package/types/layer1.d.mts +37 -0
package/README.md
CHANGED
|
@@ -60,17 +60,18 @@ Split into subpaths so the heavy HTML dependency stays opt-in. **Seam** names
|
|
|
60
60
|
the callback you inject for the agent-specific concern; `—` is a pure transform,
|
|
61
61
|
`fs (direct)` does its own file I/O instead of taking one.
|
|
62
62
|
|
|
63
|
-
| # | Import | Purpose
|
|
64
|
-
| --- | --------------- |
|
|
65
|
-
| 1 | `/invisible` | Strip zero-width, bidi, variation-selector and tag chars + ANSI/SGR escapes. Preserves ZWNJ/ZWJ for Arabic/Indic/emoji. Zero deps.
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
63
|
+
| # | Import | Purpose | Seam |
|
|
64
|
+
| --- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
|
65
|
+
| 1 | `/invisible` | Strip zero-width, bidi, variation-selector and tag chars + ANSI/SGR escapes. Preserves ZWNJ/ZWJ for Arabic/Indic/emoji. Zero deps. | — |
|
|
66
|
+
| 1 | `/layer1` | Layer 1 in one call: the invisible-char and ANSI strips run to a FIXED POINT over each other (either removal can reconstitute what the other matches), then a residual control-introducer sweep closes it — not a two-step pipeline a caller can reproduce in a fixed order. `applyLayer1` leaves an unpaired UTF-16 surrogate in place; `applyLayer1WellFormed` also maps it to U+FFFD, which is the string `/output` shows a model — take that one when the result feeds a redactor, an offset calculation or a view map. Zero deps. | — |
|
|
67
|
+
| 2 | `/html` | Splice out HTML comments and elements hidden via `display:none`, off-screen, white-on-white, `hidden`. Each splice leaves a keyed, round-trippable placeholder. | — |
|
|
68
|
+
| 3 | `/html` | Detect exfil-shaped URLs (payloads in query/path, embedded creds, `data:`/`javascript:`, off-origin redirects) and confusable HOSTS (`аpple.com`). Reports only — never rewrites. | — |
|
|
69
|
+
| 4 | `/confusables` | Fold look-alike glyphs in tool-call input (paths, commands) to ASCII, closing a cross-script deny-rule bypass. Gated per token, so non-Latin prose passes through unfolded. | `scan` (optional) |
|
|
70
|
+
| 5 | `/instructions` | Scan/auto-clean `CLAUDE.md`, `AGENTS.md`, `SKILL.md`, etc., decoding Unicode-tag + zero-width-binary payloads. | `fs` (direct) |
|
|
71
|
+
| 6 | `/prompt` | Classify a prompt pass / note / block on payload-capable invisible/ANSI content (inert escapes get the note). | — |
|
|
72
|
+
| 7 | `/output` | Run Layers 1–4 over structured tool output, preserving shape. The Layer-5 slot takes a delete-only filter. | `redact`, `filterInjection` |
|
|
73
|
+
| 8 | `/rehydrate` | Re-anchor a model Edit or whole-file Write composed from the _sanitized_ view back onto real bytes; gate MultiEdit on a verified view==disk; deny anything ambiguous or secret-exposing. | `io` |
|
|
74
|
+
| — | `/view-map` | Pure offset/text machinery mapping a file's on-disk bytes ↔ the sanitized view (Layer-1 deletions, Layer-4 redactions). No I/O — consumed by `/rehydrate`. | — |
|
|
74
75
|
|
|
75
76
|
See [`THREAT-MODEL.md`](./THREAT-MODEL.md) for per-vector detail.
|
|
76
77
|
|
|
@@ -329,13 +329,6 @@ export function failOpenContext(
|
|
|
329
329
|
);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
// Unpaired UTF-16 surrogates: a high half with no low follower, or a low half
|
|
333
|
-
// with no high lead. Hook text spliced into the model's context must be
|
|
334
|
-
// well-formed UTF-16 there, so the sanitizers normalize these out before
|
|
335
|
-
// serializing.
|
|
336
|
-
const LONE_SURROGATE_RE =
|
|
337
|
-
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
|
|
338
|
-
|
|
339
332
|
/**
|
|
340
333
|
* Hard cap on hook stdin. A well-formed Claude Code hook payload is at most a
|
|
341
334
|
* few MB (tool input plus the harness-truncated tool output); 64 MiB leaves
|
|
@@ -622,20 +615,24 @@ const UNTRUSTED_TEXT_CAP = 500;
|
|
|
622
615
|
/**
|
|
623
616
|
* Scrub untrusted text before it is spliced into the model's context via a
|
|
624
617
|
* warning/reason field: strip ANSI and payload-capable invisibles to a fixed
|
|
625
|
-
* point (via the injected `layer1
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
618
|
+
* point (via the injected `layer1`), then cap by whole code points. `layer1` is
|
|
619
|
+
* injected rather than imported so this dependency-light module never eagerly
|
|
620
|
+
* loads the sanitizer package — each caller passes its own caught-import
|
|
621
|
+
* binding.
|
|
622
|
+
*
|
|
623
|
+
* Pass a layer1 that also normalizes lone surrogates — `applyLayer1WellFormed`,
|
|
624
|
+
* never the bare `applyLayer1`. The model's UTF-16 context must be well-formed,
|
|
625
|
+
* and the code-point cap below must not slice a half it mistook for a whole
|
|
626
|
+
* character. This module used to re-spell that substitution over a private
|
|
627
|
+
* regex; the package now owns the one definition.
|
|
631
628
|
* @param {unknown} raw
|
|
632
|
-
* @param {(text: string) => { cleaned: string }} layer1
|
|
629
|
+
* @param {(text: string) => { cleaned: string }} layer1 MUST normalize lone surrogates
|
|
633
630
|
* @param {number} [cap]
|
|
634
631
|
* @returns {string}
|
|
635
632
|
*/
|
|
636
633
|
export function scrubUntrustedText(raw, layer1, cap = UNTRUSTED_TEXT_CAP) {
|
|
637
634
|
if (typeof raw !== "string" || raw === "") return "";
|
|
638
|
-
const cleaned = layer1(raw).cleaned
|
|
635
|
+
const cleaned = layer1(raw).cleaned;
|
|
639
636
|
const points = [...cleaned];
|
|
640
637
|
return points.length > cap
|
|
641
638
|
? points.slice(0, cap).join("") + "…[truncated]"
|
|
@@ -30,13 +30,16 @@ import {
|
|
|
30
30
|
} from "./hook-io.mjs";
|
|
31
31
|
|
|
32
32
|
// Layer-1 scrubber for the untrusted alert-store contents the gate splices into a
|
|
33
|
-
// permissionDecisionReason.
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
// permissionDecisionReason. The WELL-FORMED composition, not the bare applyLayer1:
|
|
34
|
+
// the reason is spliced into the model's UTF-16 context, and scrubUntrustedText's
|
|
35
|
+
// code-point cap must not slice a lone surrogate half. Bound via lazyImport (see
|
|
36
|
+
// its doc for the fail-OPEN hazard of a bare static npm import): a load failure
|
|
37
|
+
// leaves it undefined, so scrubUntrustedText throws into the caller's fail-closed
|
|
38
|
+
// catch (→ ask) rather than emitting an unscrubbed reason.
|
|
39
|
+
const { applyLayer1WellFormed } =
|
|
40
|
+
/** @type {typeof import("agent-sanitizer")} */ (
|
|
41
|
+
await lazyImport("agent-sanitizer")
|
|
42
|
+
);
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* The path prefix every alert artifact of this PROJECT shares. Never a file
|
|
@@ -392,7 +395,7 @@ export function invisibleCharAlert(sessionId) {
|
|
|
392
395
|
}
|
|
393
396
|
}
|
|
394
397
|
if (parts.length === 0) return null;
|
|
395
|
-
return scrubUntrustedText(parts.join("\n"),
|
|
398
|
+
return scrubUntrustedText(parts.join("\n"), applyLayer1WellFormed);
|
|
396
399
|
}
|
|
397
400
|
|
|
398
401
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sanitizer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.47.0",
|
|
4
4
|
"description": "Defend an agent against hidden-content injection: strip payload-capable invisible Unicode and ANSI, splice out human-invisible HTML, and flag data-exfil URLs in untrusted text before any model sees it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -98,6 +98,10 @@
|
|
|
98
98
|
"types": "./types/invisible.d.mts",
|
|
99
99
|
"default": "./src/invisible.mjs"
|
|
100
100
|
},
|
|
101
|
+
"./layer1": {
|
|
102
|
+
"types": "./types/layer1.d.mts",
|
|
103
|
+
"default": "./src/layer1.mjs"
|
|
104
|
+
},
|
|
101
105
|
"./html": {
|
|
102
106
|
"types": "./types/html.d.mts",
|
|
103
107
|
"default": "./src/html.mjs"
|
package/src/index.mjs
CHANGED
|
@@ -25,8 +25,10 @@ import { sanitizeText } from "./output.mjs";
|
|
|
25
25
|
// (`./rehydrate`) so every consumer derives the identical model-facing view.
|
|
26
26
|
export {
|
|
27
27
|
applyLayer1,
|
|
28
|
+
applyLayer1WellFormed,
|
|
28
29
|
isBenignAnsi,
|
|
29
30
|
isBenignAnsiKinds,
|
|
31
|
+
normalizeLoneSurrogates,
|
|
30
32
|
stripAnsiFully,
|
|
31
33
|
LONE_SURROGATE_RE,
|
|
32
34
|
} from "./layer1.mjs";
|
package/src/layer1.mjs
CHANGED
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
* re-implementation would drift, and rehydration's soundness gate depends on
|
|
7
7
|
* re-cleaning reproducing the view).
|
|
8
8
|
*
|
|
9
|
-
* Lone-surrogate normalization is NOT applied by {@link applyLayer1} itself
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Lone-surrogate normalization is NOT applied by {@link applyLayer1} itself. It
|
|
10
|
+
* belongs at the boundary that needs a well-formed string — the redactor input
|
|
11
|
+
* in output.mjs's processLayer1 / re-redact, and before the HTML tokenizer —
|
|
12
|
+
* because that is the point where a lone surrogate would otherwise corrupt a
|
|
13
|
+
* match or a parse. Prefer {@link normalizeLoneSurrogates} at each such
|
|
14
|
+
* boundary, the one definition of that substitution, which output.mjs's
|
|
15
|
+
* processLayer1 and rehydrate.mjs both call — not a fresh `.replace` over
|
|
16
|
+
* {@link LONE_SURROGATE_RE}.
|
|
17
|
+
*
|
|
18
|
+
* So {@link applyLayer1} alone is NOT the string the tool-output pipeline shows a
|
|
19
|
+
* model: on an input carrying an unpaired surrogate the two differ at exactly
|
|
20
|
+
* that code unit. {@link applyLayer1WellFormed} is the composition the pipeline
|
|
21
|
+
* runs, and a consumer deriving offsets for redaction or view mapping wants that
|
|
22
|
+
* one — see its own doc for why the choice matters.
|
|
14
23
|
*/
|
|
15
24
|
import { stripInvisibleWithReport, CATEGORY } from "./invisible.mjs";
|
|
16
25
|
import {
|
|
@@ -266,3 +275,48 @@ export function applyLayer1(text) {
|
|
|
266
275
|
if (ansiKinds.size > 0) found.add(CATEGORY.ANSI);
|
|
267
276
|
return { cleaned, deAnsi, found: [...found], ansiKinds: [...ansiKinds] };
|
|
268
277
|
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Map every lone UTF-16 surrogate to U+FFFD. Load-bearing on ANY path that feeds
|
|
281
|
+
* text to an injected redactor: a secret split by an interposed lone surrogate
|
|
282
|
+
* reads as adjacent to a model rendering its own UTF-16 but as broken to a
|
|
283
|
+
* redactor (Node maps the lone surrogate to U+FFFD en route), so a secret
|
|
284
|
+
* reconstituted across the surrogate survives redaction unless the text is
|
|
285
|
+
* normalized first. It also keeps an HTML tokenizer from throwing on a stray
|
|
286
|
+
* code unit. The substitution is same-LENGTH — one UTF-16 unit for one — so
|
|
287
|
+
* offsets computed against the un-normalized string stay valid against this one.
|
|
288
|
+
* @param {string} text
|
|
289
|
+
* @returns {string}
|
|
290
|
+
*/
|
|
291
|
+
export function normalizeLoneSurrogates(text) {
|
|
292
|
+
return text.replace(LONE_SURROGATE_RE, "\uFFFD");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* {@link applyLayer1} followed by {@link normalizeLoneSurrogates} — the exact
|
|
297
|
+
* composition `output.mjs`'s processLayer1 runs before Layers 2+ see the text,
|
|
298
|
+
* so this is the string a model is actually shown.
|
|
299
|
+
*
|
|
300
|
+
* Take this one, not `applyLayer1`, whenever the result feeds a redactor, an
|
|
301
|
+
* offset calculation or a view map: those consumers compare their own string to
|
|
302
|
+
* the model-facing view, and an unpaired surrogate is exactly where the two
|
|
303
|
+
* spellings diverge. Take `applyLayer1` when you want Layer 1's removals alone
|
|
304
|
+
* and intend to hand a possibly ill-formed string onward unchanged.
|
|
305
|
+
*
|
|
306
|
+
* `found` gains {@link CATEGORY.LONE_SURROGATES} exactly when the normalization
|
|
307
|
+
* changed the text, matching what the pipeline reports for the same input.
|
|
308
|
+
* `deAnsi` is untouched: it is the ANSI strip of the ORIGINAL text, the scope
|
|
309
|
+
* the long-run payload check needs.
|
|
310
|
+
* @param {string} text
|
|
311
|
+
* @returns {{ cleaned: string, deAnsi: string, found: string[], ansiKinds: string[] }}
|
|
312
|
+
*/
|
|
313
|
+
export function applyLayer1WellFormed(text) {
|
|
314
|
+
const result = applyLayer1(text);
|
|
315
|
+
const cleaned = normalizeLoneSurrogates(result.cleaned);
|
|
316
|
+
if (cleaned === result.cleaned) return result;
|
|
317
|
+
return {
|
|
318
|
+
...result,
|
|
319
|
+
cleaned,
|
|
320
|
+
found: [...result.found, CATEGORY.LONE_SURROGATES],
|
|
321
|
+
};
|
|
322
|
+
}
|
package/src/output.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
applyLayer1,
|
|
35
35
|
INERT_ANSI_NOTE,
|
|
36
36
|
isBenignAnsiKinds,
|
|
37
|
-
|
|
37
|
+
normalizeLoneSurrogates,
|
|
38
38
|
} from "./layer1.mjs";
|
|
39
39
|
import {
|
|
40
40
|
describeConfusableHosts,
|
|
@@ -144,21 +144,6 @@ function errMessage(err) {
|
|
|
144
144
|
* message). Null means the filter made no finding.
|
|
145
145
|
*/
|
|
146
146
|
|
|
147
|
-
/**
|
|
148
|
-
* Map every lone UTF-16 surrogate to U+FFFD. Load-bearing on ANY path that feeds
|
|
149
|
-
* text to the injected redactor: a secret split by an interposed lone surrogate
|
|
150
|
-
* reads as adjacent to a model rendering its own UTF-16 but as broken to a
|
|
151
|
-
* redactor (Node maps the lone surrogate to U+FFFD en route), so a secret
|
|
152
|
-
* reconstituted across the surrogate survives redaction unless the text is
|
|
153
|
-
* normalized first. Shared by {@link processLayer1} and the Layer-5 re-redact so
|
|
154
|
-
* the two redact-input paths cannot drift.
|
|
155
|
-
* @param {string} text
|
|
156
|
-
* @returns {string}
|
|
157
|
-
*/
|
|
158
|
-
function normalizeLoneSurrogates(text) {
|
|
159
|
-
return text.replace(LONE_SURROGATE_RE, "�");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
147
|
/**
|
|
163
148
|
* @typedef {{ text: string, found: string[], findings: import("./severity.mjs").Finding[], modified: boolean, unreportedChange: boolean }} PipelineState
|
|
164
149
|
* The running state of one {@link sanitizeText} call. Layers read `text` and
|
package/src/rehydrate.mjs
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
* denied with use-single-Edit guidance (see the dispatch in
|
|
55
55
|
* {@link rehydrateRedacted}).
|
|
56
56
|
*/
|
|
57
|
-
import { applyLayer1,
|
|
57
|
+
import { applyLayer1, normalizeLoneSurrogates } from "./layer1.mjs";
|
|
58
58
|
import {
|
|
59
59
|
occurrences,
|
|
60
60
|
overlapAwareCount,
|
|
@@ -114,7 +114,7 @@ function layer1View(text) {
|
|
|
114
114
|
const { cleaned: layer1Cleaned } = applyLayer1(text);
|
|
115
115
|
return {
|
|
116
116
|
layer1Cleaned,
|
|
117
|
-
cleaned: layer1Cleaned
|
|
117
|
+
cleaned: normalizeLoneSurrogates(layer1Cleaned),
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -245,14 +245,18 @@ export function makeDeadline(budgetMs: number, now?: () => number): {
|
|
|
245
245
|
/**
|
|
246
246
|
* Scrub untrusted text before it is spliced into the model's context via a
|
|
247
247
|
* warning/reason field: strip ANSI and payload-capable invisibles to a fixed
|
|
248
|
-
* point (via the injected `layer1
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
248
|
+
* point (via the injected `layer1`), then cap by whole code points. `layer1` is
|
|
249
|
+
* injected rather than imported so this dependency-light module never eagerly
|
|
250
|
+
* loads the sanitizer package — each caller passes its own caught-import
|
|
251
|
+
* binding.
|
|
252
|
+
*
|
|
253
|
+
* Pass a layer1 that also normalizes lone surrogates — `applyLayer1WellFormed`,
|
|
254
|
+
* never the bare `applyLayer1`. The model's UTF-16 context must be well-formed,
|
|
255
|
+
* and the code-point cap below must not slice a half it mistook for a whole
|
|
256
|
+
* character. This module used to re-spell that substitution over a private
|
|
257
|
+
* regex; the package now owns the one definition.
|
|
254
258
|
* @param {unknown} raw
|
|
255
|
-
* @param {(text: string) => { cleaned: string }} layer1
|
|
259
|
+
* @param {(text: string) => { cleaned: string }} layer1 MUST normalize lone surrogates
|
|
256
260
|
* @param {number} [cap]
|
|
257
261
|
* @returns {string}
|
|
258
262
|
*/
|
package/types/index.d.mts
CHANGED
|
@@ -52,6 +52,6 @@ export function sanitize(text: string, options?: {
|
|
|
52
52
|
original: string;
|
|
53
53
|
}>;
|
|
54
54
|
}>;
|
|
55
|
-
export { applyLayer1, isBenignAnsi, isBenignAnsiKinds, stripAnsiFully, LONE_SURROGATE_RE } from "./layer1.mjs";
|
|
55
|
+
export { applyLayer1, applyLayer1WellFormed, isBenignAnsi, isBenignAnsiKinds, normalizeLoneSurrogates, stripAnsiFully, LONE_SURROGATE_RE } from "./layer1.mjs";
|
|
56
56
|
export { stripInvisible, stripInvisibleWithReport, isSgrOnly, STRIP, SGR_RE, CHECKS, CATEGORY, CATEGORY_LABELS, LINGUISTIC_SCRIPTS, VS, BLANK_NON_CF, LONG_RUN_RE, LONG_RUN_THRESHOLD, SCATTERED_THRESHOLD, findLongRuns, hasLongRun } from "./invisible.mjs";
|
|
57
57
|
export { HTML_TAG_PRESENT, MD_LINK_HINT, SECRET_HINT, SECRET_HINT_EXT, matchesSecretHint } from "./gates.mjs";
|
package/types/layer1.d.mts
CHANGED
|
@@ -97,6 +97,43 @@ export function applyLayer1(text: string): {
|
|
|
97
97
|
found: string[];
|
|
98
98
|
ansiKinds: string[];
|
|
99
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* Map every lone UTF-16 surrogate to U+FFFD. Load-bearing on ANY path that feeds
|
|
102
|
+
* text to an injected redactor: a secret split by an interposed lone surrogate
|
|
103
|
+
* reads as adjacent to a model rendering its own UTF-16 but as broken to a
|
|
104
|
+
* redactor (Node maps the lone surrogate to U+FFFD en route), so a secret
|
|
105
|
+
* reconstituted across the surrogate survives redaction unless the text is
|
|
106
|
+
* normalized first. It also keeps an HTML tokenizer from throwing on a stray
|
|
107
|
+
* code unit. The substitution is same-LENGTH — one UTF-16 unit for one — so
|
|
108
|
+
* offsets computed against the un-normalized string stay valid against this one.
|
|
109
|
+
* @param {string} text
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
export function normalizeLoneSurrogates(text: string): string;
|
|
113
|
+
/**
|
|
114
|
+
* {@link applyLayer1} followed by {@link normalizeLoneSurrogates} — the exact
|
|
115
|
+
* composition `output.mjs`'s processLayer1 runs before Layers 2+ see the text,
|
|
116
|
+
* so this is the string a model is actually shown.
|
|
117
|
+
*
|
|
118
|
+
* Take this one, not `applyLayer1`, whenever the result feeds a redactor, an
|
|
119
|
+
* offset calculation or a view map: those consumers compare their own string to
|
|
120
|
+
* the model-facing view, and an unpaired surrogate is exactly where the two
|
|
121
|
+
* spellings diverge. Take `applyLayer1` when you want Layer 1's removals alone
|
|
122
|
+
* and intend to hand a possibly ill-formed string onward unchanged.
|
|
123
|
+
*
|
|
124
|
+
* `found` gains {@link CATEGORY.LONE_SURROGATES} exactly when the normalization
|
|
125
|
+
* changed the text, matching what the pipeline reports for the same input.
|
|
126
|
+
* `deAnsi` is untouched: it is the ANSI strip of the ORIGINAL text, the scope
|
|
127
|
+
* the long-run payload check needs.
|
|
128
|
+
* @param {string} text
|
|
129
|
+
* @returns {{ cleaned: string, deAnsi: string, found: string[], ansiKinds: string[] }}
|
|
130
|
+
*/
|
|
131
|
+
export function applyLayer1WellFormed(text: string): {
|
|
132
|
+
cleaned: string;
|
|
133
|
+
deAnsi: string;
|
|
134
|
+
found: string[];
|
|
135
|
+
ansiKinds: string[];
|
|
136
|
+
};
|
|
100
137
|
export const LONE_SURROGATE_RE: RegExp;
|
|
101
138
|
/**
|
|
102
139
|
* What a reader is told when the ONLY thing a strip removed was inert ANSI (see
|