@mossbear/eval-core 0.1.0-alpha.100

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/adapter.d.ts +31 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +10 -0
  6. package/dist/adapter.js.map +1 -0
  7. package/dist/credential-patterns.d.ts +9 -0
  8. package/dist/credential-patterns.d.ts.map +1 -0
  9. package/dist/credential-patterns.js +9 -0
  10. package/dist/credential-patterns.js.map +1 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +11 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/redact-prose.d.ts +77 -0
  16. package/dist/redact-prose.d.ts.map +1 -0
  17. package/dist/redact-prose.js +339 -0
  18. package/dist/redact-prose.js.map +1 -0
  19. package/dist/redact-prose.test.d.ts +2 -0
  20. package/dist/redact-prose.test.d.ts.map +1 -0
  21. package/dist/redact-prose.test.js +429 -0
  22. package/dist/redact-prose.test.js.map +1 -0
  23. package/dist/redaction.d.ts +23 -0
  24. package/dist/redaction.d.ts.map +1 -0
  25. package/dist/redaction.js +23 -0
  26. package/dist/redaction.js.map +1 -0
  27. package/dist/sanitize-command.d.ts +21 -0
  28. package/dist/sanitize-command.d.ts.map +1 -0
  29. package/dist/sanitize-command.js +908 -0
  30. package/dist/sanitize-command.js.map +1 -0
  31. package/dist/sanitize-command.test.d.ts +2 -0
  32. package/dist/sanitize-command.test.d.ts.map +1 -0
  33. package/dist/sanitize-command.test.js +867 -0
  34. package/dist/sanitize-command.test.js.map +1 -0
  35. package/dist/steering-classification.d.ts +19 -0
  36. package/dist/steering-classification.d.ts.map +1 -0
  37. package/dist/steering-classification.js +55 -0
  38. package/dist/steering-classification.js.map +1 -0
  39. package/dist/strip-code-blocks.d.ts +68 -0
  40. package/dist/strip-code-blocks.d.ts.map +1 -0
  41. package/dist/strip-code-blocks.js +280 -0
  42. package/dist/strip-code-blocks.js.map +1 -0
  43. package/dist/strip-code-blocks.test.d.ts +2 -0
  44. package/dist/strip-code-blocks.test.d.ts.map +1 -0
  45. package/dist/strip-code-blocks.test.js +250 -0
  46. package/dist/strip-code-blocks.test.js.map +1 -0
  47. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mossbear
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @mossbear/eval-core
2
+
3
+ Portable pieces of Mossbear's evaluation path — command redaction, the `EvalAdapter` LLM seam, and on-demand guide normalization. Grading itself runs on the [Mossbear](https://app.mossbear.io) dashboard; this package holds the parts that must stay importable outside it.
4
+
5
+ `sanitizeCommand` is used by the [`mossbear` CLI](https://www.npmjs.com/package/mossbear) to redact shell command text before an action is ever recorded; the adapter and normalization prompt are used server-side.
6
+
7
+ You normally don't install this directly; it ships as a dependency of `mossbear`.
8
+
9
+ Source: https://github.com/mossbear-io/mossbear (`packages/eval-core`)
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The LLM completion seam.
3
+ *
4
+ * eval-core never imports fetch or any HTTP library directly — a caller
5
+ * supplies the transport and this package stays dependency-free and testable.
6
+ * The dashboard implements it in `src/lib/eval/serverEvalAdapter.ts`; tests
7
+ * pass mocks.
8
+ */
9
+ /** Token counts for one completion call, when an adapter can report them. */
10
+ export type EvalUsage = {
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ cacheReadTokens?: number;
14
+ cacheWriteTokens?: number;
15
+ };
16
+ /** Adapter interface for LLM completion. */
17
+ export interface EvalAdapter {
18
+ complete(system: string, user: string): Promise<string>;
19
+ /**
20
+ * Optional: adapters that can report token usage alongside a completion's
21
+ * text implement this instead of (or in addition to) `complete`. Callers
22
+ * that care about usage/cost attribution
23
+ * call this when present; an adapter without it is still a valid
24
+ * `EvalAdapter`.
25
+ */
26
+ completeWithUsage?(system: string, user: string): Promise<{
27
+ text: string;
28
+ usage: EvalUsage | null;
29
+ }>;
30
+ }
31
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,4CAA4C;AAC5C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvD;;;;;;OAMG;IACH,iBAAiB,CAAC,CAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;CACtD"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The LLM completion seam.
3
+ *
4
+ * eval-core never imports fetch or any HTTP library directly — a caller
5
+ * supplies the transport and this package stays dependency-free and testable.
6
+ * The dashboard implements it in `src/lib/eval/serverEvalAdapter.ts`; tests
7
+ * pass mocks.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,9 @@
1
+ /** Syntax shared by the command and prose credential scanners. */
2
+ export declare const SENSITIVE_WORD_SOURCE = "(?:api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|authorization|token|secret|passw(?:or)?d|credentials?)";
3
+ export declare const SENSITIVE_KEY_SOURCE = "[\\w.-]*(?:api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|authorization|token|secret|passw(?:or)?d|credentials?)[\\w.-]*";
4
+ export declare const SHELL_WORD_SOURCE: string;
5
+ /** Whole segments in prose, avoiding ordinary words such as secretary. */
6
+ export declare const PROSE_SENSITIVE_KEY_SOURCE = "(?:[A-Za-z0-9]+[._-])*(?:api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|authorization|token|secret|passw(?:or)?d|credentials?)(?:[._-][A-Za-z0-9]+)*";
7
+ /** Cookie pairs, bounded by header quoting or the next prose/shell word. */
8
+ export declare const COOKIE_HEADER_SOURCE: string;
9
+ //# sourceMappingURL=credential-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-patterns.d.ts","sourceRoot":"","sources":["../src/credential-patterns.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,0IACuG,CAAA;AACzI,eAAO,MAAM,oBAAoB,0JAA6C,CAAA;AAC9E,eAAO,MAAM,iBAAiB,QAAgG,CAAA;AAE9H,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,sLAAyE,CAAA;AAEhH,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,QAAuN,CAAA"}
@@ -0,0 +1,9 @@
1
+ /** Syntax shared by the command and prose credential scanners. */
2
+ export const SENSITIVE_WORD_SOURCE = '(?:api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|client[_-]?secret|authorization|token|secret|passw(?:or)?d|credentials?)';
3
+ export const SENSITIVE_KEY_SOURCE = `[\\w.-]*${SENSITIVE_WORD_SOURCE}[\\w.-]*`;
4
+ export const SHELL_WORD_SOURCE = String.raw `(?:\$'(?:\\[\s\S]|[^'\\])*'|'[^']*'|"(?:\\.|[^"\\])*"|\\[\s\S]|[^\s;&|()<>'"\\])+`;
5
+ /** Whole segments in prose, avoiding ordinary words such as secretary. */
6
+ export const PROSE_SENSITIVE_KEY_SOURCE = `(?:[A-Za-z0-9]+[._-])*${SENSITIVE_WORD_SOURCE}(?:[._-][A-Za-z0-9]+)*`;
7
+ /** Cookie pairs, bounded by header quoting or the next prose/shell word. */
8
+ export const COOKIE_HEADER_SOURCE = String.raw `\b((?:set-cookie|cookie)[ \t]*:[ \t]*)(?:[\w.!#$%&*+^\x60|~-]+[ \t]*=[ \t]*(?:"(?:\\.|[^"\\\r\n])*"|[^\s;"',]*))(?:[ \t]*;[ \t]*[\w.!#$%&*+^\x60|~-]+[ \t]*=[ \t]*(?:"(?:\\.|[^"\\\r\n])*"|[^\s;"',]*))*`;
9
+ //# sourceMappingURL=credential-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-patterns.js","sourceRoot":"","sources":["../src/credential-patterns.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,qBAAqB,GAChC,uIAAuI,CAAA;AACzI,MAAM,CAAC,MAAM,oBAAoB,GAAG,WAAW,qBAAqB,UAAU,CAAA;AAC9E,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC,GAAG,CAAA,mFAAmF,CAAA;AAE9H,0EAA0E;AAC1E,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,qBAAqB,wBAAwB,CAAA;AAEhH,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAA,0MAA0M,CAAA"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @mossbear/eval-core
3
+ *
4
+ * Pure evaluation logic with no filesystem, network, or secret-loading side effects.
5
+ * This package must remain importable outside the CLI package.
6
+ */
7
+ export { type EvalAdapter, type EvalUsage } from './adapter.js';
8
+ export { sanitizeCommand } from './sanitize-command.js';
9
+ export { stripCodeBlocks, type CodeBlockStripResult } from './strip-code-blocks.js';
10
+ export { STEERING_LABELS, STEERING_CLASSIFIER_VERSION, classifySteeringTurn, classifySteeringTurnV1, scoreSteeringBaseline, type SteeringBaselineScore, type SteeringLabel, } from './steering-classification.js';
11
+ export { MAX_PROSE_CHARS, PROSE_REDACTION_CLASSES, redactProse, type ProseRedactionClass, type ProseRedactionReport, } from './redact-prose.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAEnF,OAAO,EACL,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @mossbear/eval-core
3
+ *
4
+ * Pure evaluation logic with no filesystem, network, or secret-loading side effects.
5
+ * This package must remain importable outside the CLI package.
6
+ */
7
+ export { sanitizeCommand } from './sanitize-command.js';
8
+ export { stripCodeBlocks } from './strip-code-blocks.js';
9
+ export { STEERING_LABELS, STEERING_CLASSIFIER_VERSION, classifySteeringTurn, classifySteeringTurnV1, scoreSteeringBaseline, } from './steering-classification.js';
10
+ export { MAX_PROSE_CHARS, PROSE_REDACTION_CLASSES, redactProse, } from './redact-prose.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,OAAO,EAAE,eAAe,EAA6B,MAAM,wBAAwB,CAAA;AAEnF,OAAO,EACL,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,GAGtB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,WAAW,GAGZ,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Secret redaction and length-capping for **prose** — transcript message text,
3
+ * as opposed to the shell command text `sanitizeCommand` handles.
4
+ *
5
+ * Why a second redactor rather than reusing the first: `sanitizeCommand` is
6
+ * pattern-matched against *shell syntax* — heredocs, `KEY=value` anchored at a
7
+ * shell token boundary, `Authorization:` headers
8
+ * (`./sanitize-command.ts:31-49`). `transcript-content-classes-plan.md:20`
9
+ * records that it "catches none of it in prose" and names a prose redactor the
10
+ * load-bearing deliverable rather than a checkbox on one. This is that gate.
11
+ *
12
+ * It lives here beside `sanitizeCommand`, and not in the CLI, because the plan
13
+ * asks for exactly that (`transcript-content-classes-plan.md:43`): shared, so
14
+ * the CLI and any future server-side path cannot drift into different
15
+ * strengths of the same protection.
16
+ *
17
+ * **What this is not.** This is the redactor and nothing else: it does not
18
+ * extract transcript messages and does not strip code blocks. It is no longer
19
+ * true, as this header said through Slice 1, that "no caller uploads its
20
+ * output" — transcript narration ships, and `redactProse` runs on it
21
+ * (`../../cli/src/transcript/narration.ts:133`) before it is attached to an
22
+ * action at the `action-queue.ts` choke point and uploaded by `mossbear sync`.
23
+ * This function is now the last thing between prose and the wire, which is why
24
+ * a gap in the pattern list below is a live disclosure rather than a latent
25
+ * one. Two consequences of that are worth saying out loud rather than
26
+ * assuming:
27
+ *
28
+ * - **Code-block stripping is structural and happens before this runs.**
29
+ * Fenced and indented blocks come out by the Markdown structure of the
30
+ * message (Slice 2). A regex over prose must never be what stands between
31
+ * a code block and the wire, so nothing here attempts it.
32
+ * - **Redaction is best-effort; the length cap is the backstop.** The same
33
+ * admission `sanitizeCommand` makes (`./sanitize-command.ts:26-29`). A
34
+ * pattern list cannot enumerate every
35
+ * secret shape, which is exactly why the class table strips whole classes
36
+ * rather than trusting a scrubber to find secrets inside them.
37
+ *
38
+ * Reports **what it redacted, by class and count**, so a disclosure surface can
39
+ * show the user what was removed rather than asserting safety at them.
40
+ */
41
+ /**
42
+ * Maximum characters of a single prose message that survive redaction.
43
+ *
44
+ * Deliberately far larger than `sanitizeCommand`'s 200
45
+ * (`./sanitize-command.ts:20`): a command is a tool
46
+ * invocation, where anything long is bulk payload, whereas prose is the
47
+ * content — "I'm going to X because Y" is the whole reason this class syncs at
48
+ * all, and truncating it to a couple of sentences would leave the grader with
49
+ * the same intent-free evidence it has today. Long enough for a real
50
+ * explanation, short enough that a pasted file body cannot ride along whole.
51
+ */
52
+ export declare const MAX_PROSE_CHARS = 2000;
53
+ /**
54
+ * The classes this reports on. Named rather than free-form strings so a
55
+ * disclosure surface can label each count without re-deriving the taxonomy —
56
+ * and so adding a pattern forces a decision about which class it belongs to.
57
+ */
58
+ export declare const PROSE_REDACTION_CLASSES: readonly ["private_key", "url_credentials", "assignment", "token", "email", "long_random_string"];
59
+ export type ProseRedactionClass = (typeof PROSE_REDACTION_CLASSES)[number];
60
+ export type ProseRedactionReport = {
61
+ /** The redacted text, length-capped. */
62
+ text: string;
63
+ /** How many spans were replaced, per class. Absent classes redacted nothing. */
64
+ counts: Partial<Record<ProseRedactionClass, number>>;
65
+ /** Total spans replaced across all classes. */
66
+ redactedCount: number;
67
+ /** The text hit `MAX_PROSE_CHARS` and lost its tail. */
68
+ wasTruncated: boolean;
69
+ };
70
+ /**
71
+ * Redact secrets from prose and cap its length, reporting what was removed.
72
+ *
73
+ * Runs on-device, before upload, always — the server never receives the
74
+ * unredacted form and therefore cannot leak it.
75
+ */
76
+ export declare function redactProse(input: string | null | undefined): ProseRedactionReport;
77
+ //# sourceMappingURL=redact-prose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-prose.d.ts","sourceRoot":"","sources":["../src/redact-prose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAUH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,OAAO,CAAA;AAwBnC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,mGAO1B,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1E,MAAM,MAAM,oBAAoB,GAAG;IACjC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,gFAAgF;IAChF,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAA;IACrB,wDAAwD;IACxD,YAAY,EAAE,OAAO,CAAA;CACtB,CAAA;AA8ID;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAgIlF"}
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Secret redaction and length-capping for **prose** — transcript message text,
3
+ * as opposed to the shell command text `sanitizeCommand` handles.
4
+ *
5
+ * Why a second redactor rather than reusing the first: `sanitizeCommand` is
6
+ * pattern-matched against *shell syntax* — heredocs, `KEY=value` anchored at a
7
+ * shell token boundary, `Authorization:` headers
8
+ * (`./sanitize-command.ts:31-49`). `transcript-content-classes-plan.md:20`
9
+ * records that it "catches none of it in prose" and names a prose redactor the
10
+ * load-bearing deliverable rather than a checkbox on one. This is that gate.
11
+ *
12
+ * It lives here beside `sanitizeCommand`, and not in the CLI, because the plan
13
+ * asks for exactly that (`transcript-content-classes-plan.md:43`): shared, so
14
+ * the CLI and any future server-side path cannot drift into different
15
+ * strengths of the same protection.
16
+ *
17
+ * **What this is not.** This is the redactor and nothing else: it does not
18
+ * extract transcript messages and does not strip code blocks. It is no longer
19
+ * true, as this header said through Slice 1, that "no caller uploads its
20
+ * output" — transcript narration ships, and `redactProse` runs on it
21
+ * (`../../cli/src/transcript/narration.ts:133`) before it is attached to an
22
+ * action at the `action-queue.ts` choke point and uploaded by `mossbear sync`.
23
+ * This function is now the last thing between prose and the wire, which is why
24
+ * a gap in the pattern list below is a live disclosure rather than a latent
25
+ * one. Two consequences of that are worth saying out loud rather than
26
+ * assuming:
27
+ *
28
+ * - **Code-block stripping is structural and happens before this runs.**
29
+ * Fenced and indented blocks come out by the Markdown structure of the
30
+ * message (Slice 2). A regex over prose must never be what stands between
31
+ * a code block and the wire, so nothing here attempts it.
32
+ * - **Redaction is best-effort; the length cap is the backstop.** The same
33
+ * admission `sanitizeCommand` makes (`./sanitize-command.ts:26-29`). A
34
+ * pattern list cannot enumerate every
35
+ * secret shape, which is exactly why the class table strips whole classes
36
+ * rather than trusting a scrubber to find secrets inside them.
37
+ *
38
+ * Reports **what it redacted, by class and count**, so a disclosure surface can
39
+ * show the user what was removed rather than asserting safety at them.
40
+ */
41
+ import { COOKIE_HEADER_SOURCE, PROSE_SENSITIVE_KEY_SOURCE, SENSITIVE_KEY_SOURCE, SHELL_WORD_SOURCE, } from './credential-patterns.js';
42
+ import { PRIVATE_KEY_BLOCK_SOURCE, REDACTED } from './redaction.js';
43
+ /**
44
+ * Maximum characters of a single prose message that survive redaction.
45
+ *
46
+ * Deliberately far larger than `sanitizeCommand`'s 200
47
+ * (`./sanitize-command.ts:20`): a command is a tool
48
+ * invocation, where anything long is bulk payload, whereas prose is the
49
+ * content — "I'm going to X because Y" is the whole reason this class syncs at
50
+ * all, and truncating it to a couple of sentences would leave the grader with
51
+ * the same intent-free evidence it has today. Long enough for a real
52
+ * explanation, short enough that a pasted file body cannot ride along whole.
53
+ */
54
+ export const MAX_PROSE_CHARS = 2000;
55
+ /**
56
+ * Characters actually scanned by the patterns below.
57
+ *
58
+ * The cap on the *output* is not a bound on the *work*: without this, every
59
+ * pattern scans an arbitrarily long untrusted message before a single
60
+ * character is discarded, and the assignment matcher backtracks badly on
61
+ * adversarial input (a long run of `SECRET` with no `=` after it). A pasted
62
+ * megabyte could then stall CLI sync to produce 2,000 characters.
63
+ *
64
+ * Generous rather than equal to `MAX_PROSE_CHARS` because redaction *shrinks*
65
+ * text — a secret replaced by `[redacted]` pulls later content into the
66
+ * surviving window — so the scan has to see more than it will keep. Anything
67
+ * past this is dropped before scanning and counts as truncation.
68
+ */
69
+ const MAX_SCAN_CHARS = MAX_PROSE_CHARS * 8;
70
+ const QUOTED_VALUE_SOURCE = String.raw `(?:"(?:[^"\\]|\\[\s\S])*(?:"|$)|'(?:[^'\\]|\\[\s\S])*(?:'|$))`;
71
+ // Python concatenates adjacent quoted literals. Do not consume a following
72
+ // YAML key as another fragment of the value.
73
+ const MAPPING_VALUE_SOURCE = String.raw `${QUOTED_VALUE_SOURCE}(?:\s*${QUOTED_VALUE_SOURCE}(?!\s*:))*|[^,}\s]+`;
74
+ const QUOTED_COMMAND_WORD_SOURCE = String.raw `(?:\$'(?:[^'\\]|\\[\s\S])*(?:'|$)(?:${SHELL_WORD_SOURCE})?|${SHELL_WORD_SOURCE})`;
75
+ /**
76
+ * The classes this reports on. Named rather than free-form strings so a
77
+ * disclosure surface can label each count without re-deriving the taxonomy —
78
+ * and so adding a pattern forces a decision about which class it belongs to.
79
+ */
80
+ export const PROSE_REDACTION_CLASSES = [
81
+ 'private_key',
82
+ 'url_credentials',
83
+ 'assignment',
84
+ 'token',
85
+ 'email',
86
+ 'long_random_string',
87
+ ];
88
+ /**
89
+ * Ordered redactions. Order matters the same way it does in `sanitizeCommand`:
90
+ * the widest structural shapes run first, so a token *inside* a PEM block or a
91
+ * URL is already gone before the narrower patterns scan for it.
92
+ */
93
+ const PROSE_REDACTIONS = [
94
+ // Whole private-key blocks, including an unterminated one — a truncated key
95
+ // is still a key. First, because everything else would otherwise match the
96
+ // base64 inside it.
97
+ ['private_key', new RegExp(PRIVATE_KEY_BLOCK_SOURCE, 'g'), REDACTED],
98
+ // `Authorization: Bearer <token>` / `Basic <blob>`, as pasted from a request
99
+ // or a curl transcript. These values are routinely short-lived and short —
100
+ // too short for the prefix and long-run rules below — and `Authorization` is
101
+ // a header name rather than a sensitive assignment key, so without this rule
102
+ // a pasted header uploads a live credential verbatim. `sanitizeCommand`
103
+ // carries the same pattern for command text (`./sanitize-command.ts:45`).
104
+ [
105
+ 'token',
106
+ /\b(authorization\s*:\s*)(?:bearer\s+|basic\s+|token\s+)?\S+/gi,
107
+ `$1${REDACTED}`,
108
+ ],
109
+ ['token', new RegExp(COOKIE_HEADER_SOURCE, 'gi'), `$1${REDACTED}`],
110
+ [
111
+ 'assignment',
112
+ new RegExp(String.raw `("${SENSITIVE_KEY_SOURCE}"\s*:\s*)(?:${MAPPING_VALUE_SOURCE})`, 'gi'),
113
+ `$1"${REDACTED}"`,
114
+ ],
115
+ // Quoted keys in Python/YAML mappings carry the same secrets as JSON.
116
+ [
117
+ 'assignment',
118
+ new RegExp(String.raw `('${SENSITIVE_KEY_SOURCE}'\s*:\s*)(?:${MAPPING_VALUE_SOURCE})`, 'gi'),
119
+ `$1${REDACTED}`,
120
+ ],
121
+ // URL userinfo. Both shapes: `scheme://user:secret@host` and the
122
+ // token-as-sole-userinfo form `scheme://token@host`, which a colon-requiring
123
+ // pattern misses entirely and which no other rule here catches when the token
124
+ // is short. The host always stays — "it failed against api.example.com" is
125
+ // the part worth grading.
126
+ [
127
+ 'url_credentials',
128
+ /\b([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+(?::[^@\s]*)?@/gi,
129
+ `$1${REDACTED}@`,
130
+ ],
131
+ // A sensitive key followed by `=` or `:` and a value. Unlike the command
132
+ // version this is *not* anchored at a shell-token boundary — in prose the
133
+ // pair is embedded in a sentence ("I set OPENAI_API_KEY=sk-… and reran"), so
134
+ // anchoring on shell syntax is precisely what misses it.
135
+ //
136
+ // The sensitive word must be a **whole `_`/`-` separated segment** of the
137
+ // key, not a substring of it. As an arbitrary substring, `SECRET` matches
138
+ // `secretary` and `TOKEN` matches `tokenization`, so ordinary prose like
139
+ // "secretary: Alice" lost its value — and prose scrubbed into nonsense is
140
+ // useless to a grader and alarming to a reader, which makes over-redaction a
141
+ // defect here rather than a safe default.
142
+ //
143
+ // The quoted-value arms consume escape sequences (`\\.`) rather than stopping
144
+ // at the first inner quote: a value like `"correct horse \\"battery staple"`
145
+ // otherwise terminates early and leaves the tail of the password in place.
146
+ [
147
+ 'assignment',
148
+ new RegExp(String.raw `\b(${PROSE_SENSITIVE_KEY_SOURCE})(\s*[=:]\s*)(?:\$'(?:[^'\\]|\\[\s\S])*(?:'|$)(?:${SHELL_WORD_SOURCE})?|(?:"(?:[^"\\]|\\[\s\S])*(?:"|$)|'(?:[^'\\]|\\[\s\S])*(?:'|$))(?:[^\s"']+|"(?:[^"\\]|\\[\s\S])*(?:"|$)|'(?:[^'\\]|\\[\s\S])*(?:'|$))*|[^\s]+)`, 'gi'),
149
+ `$1$2${REDACTED}`,
150
+ ],
151
+ // Well-known bare token shapes, by their published prefixes. Prefix-anchored
152
+ // rather than entropy-guessed, so an ordinary long word is never mistaken for
153
+ // a credential.
154
+ //
155
+ // **Our own CLI token leads the list** (`API_KEY_PREFIX`, the prefix the
156
+ // dashboard mints CLI tokens under), for the same reason `sanitizeCommand`
157
+ // carries it (`./sanitize-command.ts:59-68`): the product's
158
+ // own secret shape is the one most likely to appear. In prose the `assignment`
159
+ // rule above only catches the pair form — `token: mossbear_…` — while the
160
+ // sentence form a user actually writes ("I set my token to mossbear_… and it
161
+ // worked") and a bare mention have no `key=value` to anchor on. The
162
+ // `long_random_string` backstop does not reach them either: a `mossbear_` key
163
+ // is under its 40-character floor. Without this arm, a user who mentions
164
+ // their token in a Claude Code message uploads a live dashboard credential in
165
+ // plaintext. Any future re-mint under a new prefix has to be added here in the
166
+ // same change, and to `sanitizeCommand` — a token neither redactor matches is
167
+ // one that ships verbatim.
168
+ //
169
+ // `auden_` is the pre-rename prefix. Every key issued under it is deleted by
170
+ // migration `0056-revoke_legacy_prefix_cli_tokens.ts`, so it matches nothing
171
+ // that can still authenticate; it is kept because a CLI redacting on the
172
+ // user's machine can be older than the dashboard it syncs to, and that window
173
+ // is exactly the one where dropping the rule uploads a live key.
174
+ //
175
+ // The `{8,}` floor matches `sanitizeCommand`'s, deliberately: the two
176
+ // redactors guard the same secret against the same upload, and a prose-side
177
+ // floor set higher would be a difference in strength with nothing behind it.
178
+ [
179
+ 'token',
180
+ /\b(?:mossbear_[A-Za-z0-9_-]{8,}|auden_[A-Za-z0-9_-]{8,}|sk-[A-Za-z0-9_-]{16,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[abposr]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[A-Za-z0-9_-]{20,})/g,
181
+ REDACTED,
182
+ ],
183
+ // Quoted command values in narration must be consumed as complete shell
184
+ // words. A bare word after a flag may be prose ("--token to authenticate").
185
+ [
186
+ 'assignment',
187
+ new RegExp(String.raw `(--${PROSE_SENSITIVE_KEY_SOURCE}\s+)(?=\$?["'])${QUOTED_COMMAND_WORD_SOURCE}`, 'gi'),
188
+ `$1${REDACTED}`,
189
+ ],
190
+ // Email addresses, listed among the plan's targets
191
+ // (`transcript-content-classes-plan.md:43`).
192
+ ['email', /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, REDACTED],
193
+ // Long unbroken base64/hex runs: a credential shape this list has no prefix
194
+ // for. Runs last, so anything a named pattern would have caught is gone.
195
+ //
196
+ // Two constraints keep it from eating ordinary text, and both are load-bearing:
197
+ //
198
+ // - **Delimiter lookarounds, not `\b`.** `+` and `/` are in the base64
199
+ // alphabet but are not word characters, so a blob beginning with one has
200
+ // no word boundary to match at — `\b` would start a character late and
201
+ // leave a 40-character secret one short of the floor, passing it through
202
+ // whole.
203
+ // - **It must contain a digit.** Length alone is not a random-string
204
+ // signal: `createAuthenticatedDashboardRequestContext` is 42 characters
205
+ // of ordinary identifier, and redacting it removes the exact detail a
206
+ // grader needs. Digests, ids and encoded blobs carry digits; long
207
+ // camel-case names do not. An all-alphabetic secret is the accepted
208
+ // miss, and it is what the named patterns above and the cap exist for.
209
+ [
210
+ 'long_random_string',
211
+ /(?<![A-Za-z0-9+/=_-])(?=[A-Za-z0-9+/_-]*\d)[A-Za-z0-9+/_-]{40,}={0,2}(?![A-Za-z0-9+/=_-])/g,
212
+ REDACTED,
213
+ ],
214
+ ];
215
+ /**
216
+ * Redact secrets from prose and cap its length, reporting what was removed.
217
+ *
218
+ * Runs on-device, before upload, always — the server never receives the
219
+ * unredacted form and therefore cannot leak it.
220
+ */
221
+ export function redactProse(input) {
222
+ if (typeof input !== 'string' || input === '') {
223
+ return { text: '', counts: {}, redactedCount: 0, wasTruncated: false };
224
+ }
225
+ // Bounded before any pattern runs — see `MAX_SCAN_CHARS`.
226
+ const wasScanTruncated = input.length > MAX_SCAN_CHARS;
227
+ let text = wasScanTruncated ? input.slice(0, MAX_SCAN_CHARS) : input;
228
+ // Decode only credential key spellings. JSON escapes must not disguise a
229
+ // sensitive key, while unrelated prose retains its original spelling.
230
+ const sensitiveKey = new RegExp(`^${SENSITIVE_KEY_SOURCE}$`, 'i');
231
+ text = text.replace(/("(?:[^"\\]|\\.)*")(\s*:)/g, (match, key, separator) => {
232
+ try {
233
+ const decoded = JSON.parse(key);
234
+ return typeof decoded === 'string' && sensitiveKey.test(decoded)
235
+ ? `${JSON.stringify(decoded)}${separator}`
236
+ : match;
237
+ }
238
+ catch {
239
+ return match;
240
+ }
241
+ });
242
+ const counts = {};
243
+ let redactedCount = 0;
244
+ const recordAssignment = () => {
245
+ counts.assignment = (counts.assignment ?? 0) + 1;
246
+ redactedCount += 1;
247
+ };
248
+ // A YAML block scalar's value is the indented lines after its marker.
249
+ // Redacting only `|` or `>` leaves the complete credential behind.
250
+ const lines = text.split('\n');
251
+ const scalarKey = new RegExp(String.raw `^([ \t]*)(-[ \t]+)?(["']?${SENSITIVE_KEY_SOURCE}["']?[ \t]*:[ \t]*)[|>](?:[+-][1-9]?|[1-9][+-]?)?[ \t]*(?:#[^\r]*)?\r?$`, 'i');
252
+ for (let index = 0; index < lines.length; index += 1) {
253
+ const match = scalarKey.exec(lines[index]);
254
+ if (!match)
255
+ continue;
256
+ const mappingIndent = match[1].length + (match[2]?.length ?? 0);
257
+ let end = index + 1;
258
+ while (end < lines.length) {
259
+ const line = lines[end];
260
+ if (line.trim() && /^[ \t]*/.exec(line)[0].length <= mappingIndent)
261
+ break;
262
+ end += 1;
263
+ }
264
+ // These credential names are valid plain YAML keys. Removing their quotes
265
+ // prevents the later JSON-key rule from treating sibling YAML as a value.
266
+ const keyPrefix = match[3].replace(/^["']|["'](?=[ \t]*:)/g, '');
267
+ lines.splice(index, end - index, `${match[1]}${match[2] ?? ''}${keyPrefix}${REDACTED}`);
268
+ recordAssignment();
269
+ }
270
+ text = lines.join('\n');
271
+ // JSON strings can contain another serialized object. Scan quoted tokens
272
+ // structurally before the flat pair patterns, with a bounded recursion.
273
+ const redactEncoded = (value, depth) => {
274
+ if (depth > 20) {
275
+ recordAssignment();
276
+ return REDACTED;
277
+ }
278
+ if (typeof value === 'string' && /^\s*[[{]/.test(value)) {
279
+ try {
280
+ const parsed = JSON.parse(value);
281
+ const redacted = redactEncoded(parsed, depth + 1);
282
+ return JSON.stringify(redacted) === JSON.stringify(parsed)
283
+ ? value
284
+ : JSON.stringify(redacted);
285
+ }
286
+ catch {
287
+ return sensitiveKey.test(value) ? REDACTED : value;
288
+ }
289
+ }
290
+ if (Array.isArray(value))
291
+ return value.map((item) => redactEncoded(item, depth + 1));
292
+ if (value === null || typeof value !== 'object')
293
+ return value;
294
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => {
295
+ if (sensitiveKey.test(key)) {
296
+ recordAssignment();
297
+ return [key, REDACTED];
298
+ }
299
+ return [key, redactEncoded(item, depth + 1)];
300
+ }));
301
+ };
302
+ text = text.replace(/"(?:[^"\\]|\\[\s\S])*(?:"|$)/g, (token) => {
303
+ try {
304
+ const value = JSON.parse(token);
305
+ const redacted = redactEncoded(value, 0);
306
+ return redacted === value ? token : JSON.stringify(redacted);
307
+ }
308
+ catch {
309
+ return token;
310
+ }
311
+ });
312
+ for (const [redactionClass, pattern, replacement] of PROSE_REDACTIONS) {
313
+ // Counted before replacing, and against a fresh regex, so the shared
314
+ // literal's `lastIndex` cannot carry between calls — a stateful `g` regex
315
+ // silently skipping the first match of the next message is exactly the
316
+ // failure a redactor must not have.
317
+ const counter = new RegExp(pattern.source, pattern.flags);
318
+ const matches = text.match(counter);
319
+ if (!matches || matches.length === 0)
320
+ continue;
321
+ // Structural passes leave redaction markers behind. Re-matching one is
322
+ // not a second replacement of the credential that produced it.
323
+ const single = new RegExp(pattern.source, pattern.flags.replace('g', ''));
324
+ const changed = matches.filter((match) => match.replace(single, replacement) !== match).length;
325
+ if (changed === 0)
326
+ continue;
327
+ counts[redactionClass] = (counts[redactionClass] ?? 0) + changed;
328
+ redactedCount += changed;
329
+ text = text.replace(new RegExp(pattern.source, pattern.flags), replacement);
330
+ }
331
+ const wasTruncated = wasScanTruncated || text.length > MAX_PROSE_CHARS;
332
+ if (text.length > MAX_PROSE_CHARS) {
333
+ // Marked rather than silently cut, so a reader can tell a short message
334
+ // from a long one that lost its tail.
335
+ text = `${text.slice(0, MAX_PROSE_CHARS)}… [truncated]`;
336
+ }
337
+ return { text, counts, redactedCount, wasTruncated };
338
+ }
339
+ //# sourceMappingURL=redact-prose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-prose.js","sourceRoot":"","sources":["../src/redact-prose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=redact-prose.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact-prose.test.d.ts","sourceRoot":"","sources":["../src/redact-prose.test.ts"],"names":[],"mappings":""}