@vibe-agent-toolkit/agent-skills 0.2.0-rc.2 → 0.2.0-rc.4
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/dist/files-config.d.ts +34 -1
- package/dist/files-config.d.ts.map +1 -1
- package/dist/files-config.js +280 -36
- package/dist/files-config.js.map +1 -1
- package/dist/fs-attribution.d.ts +45 -0
- package/dist/fs-attribution.d.ts.map +1 -0
- package/dist/fs-attribution.js +57 -0
- package/dist/fs-attribution.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts +1 -1
- package/dist/inventory/detectors/declared-but-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts +1 -1
- package/dist/inventory/detectors/marketplace-source-missing.d.ts.map +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts +1 -1
- package/dist/inventory/detectors/present-but-undeclared.d.ts.map +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts +1 -1
- package/dist/inventory/detectors/reference-target-missing.d.ts.map +1 -1
- package/dist/inventory/index.d.ts +1 -1
- package/dist/inventory/index.d.ts.map +1 -1
- package/dist/inventory/index.js +1 -1
- package/dist/inventory/index.js.map +1 -1
- package/dist/inventory/serialize.d.ts +4 -4
- package/dist/inventory/serialize.d.ts.map +1 -1
- package/dist/inventory/serialize.js +6 -6
- package/dist/inventory/serialize.js.map +1 -1
- package/dist/plugin-distribution-layout.d.ts.map +1 -1
- package/dist/plugin-distribution-layout.js +3 -1
- package/dist/plugin-distribution-layout.js.map +1 -1
- package/dist/post-build-checks.d.ts +1 -1
- package/dist/post-build-checks.d.ts.map +1 -1
- package/dist/post-build-checks.js.map +1 -1
- package/dist/projection/skill-extent.d.ts +247 -0
- package/dist/projection/skill-extent.d.ts.map +1 -0
- package/dist/projection/skill-extent.js +558 -0
- package/dist/projection/skill-extent.js.map +1 -0
- package/dist/schemas/agent-skill-frontmatter.js +1 -1
- package/dist/schemas/agent-skill-frontmatter.js.map +1 -1
- package/dist/skill-packager.d.ts +45 -7
- package/dist/skill-packager.d.ts.map +1 -1
- package/dist/skill-packager.js +123 -25
- package/dist/skill-packager.js.map +1 -1
- package/dist/skill-source/git-clone.d.ts +1 -1
- package/dist/skill-source/git-clone.d.ts.map +1 -1
- package/dist/skill-source/git-clone.js +10 -8
- package/dist/skill-source/git-clone.js.map +1 -1
- package/dist/skill-source/sources/url-source.d.ts.map +1 -1
- package/dist/skill-source/sources/url-source.js +2 -1
- package/dist/skill-source/sources/url-source.js.map +1 -1
- package/dist/skill-test/baseline-delta.d.ts +1575 -0
- package/dist/skill-test/baseline-delta.d.ts.map +1 -0
- package/dist/skill-test/baseline-delta.js +272 -0
- package/dist/skill-test/baseline-delta.js.map +1 -0
- package/dist/skill-test/baseline-integrity.d.ts +944 -0
- package/dist/skill-test/baseline-integrity.d.ts.map +1 -0
- package/dist/skill-test/baseline-integrity.js +3083 -0
- package/dist/skill-test/baseline-integrity.js.map +1 -0
- package/dist/skill-test/configure-writer.js +1 -1
- package/dist/skill-test/configure-writer.js.map +1 -1
- package/dist/skill-test/declared-env.d.ts.map +1 -1
- package/dist/skill-test/declared-env.js +2 -1
- package/dist/skill-test/declared-env.js.map +1 -1
- package/dist/skill-test/eval-executor.d.ts +20 -4
- package/dist/skill-test/eval-executor.d.ts.map +1 -1
- package/dist/skill-test/eval-executor.js +4 -4
- package/dist/skill-test/eval-executor.js.map +1 -1
- package/dist/skill-test/eval-fragment.d.ts +80 -6
- package/dist/skill-test/eval-fragment.d.ts.map +1 -1
- package/dist/skill-test/eval-fragment.js +61 -7
- package/dist/skill-test/eval-fragment.js.map +1 -1
- package/dist/skill-test/eval-grader.d.ts +1 -1
- package/dist/skill-test/eval-grader.d.ts.map +1 -1
- package/dist/skill-test/eval-grader.js +145 -8
- package/dist/skill-test/eval-grader.js.map +1 -1
- package/dist/skill-test/eval-inputs.d.ts +107 -6
- package/dist/skill-test/eval-inputs.d.ts.map +1 -1
- package/dist/skill-test/eval-inputs.js +173 -16
- package/dist/skill-test/eval-inputs.js.map +1 -1
- package/dist/skill-test/eval-lint.d.ts +13 -0
- package/dist/skill-test/eval-lint.d.ts.map +1 -1
- package/dist/skill-test/eval-lint.js +17 -4
- package/dist/skill-test/eval-lint.js.map +1 -1
- package/dist/skill-test/executor-prompt.d.ts +13 -5
- package/dist/skill-test/executor-prompt.d.ts.map +1 -1
- package/dist/skill-test/executor-prompt.js +8 -5
- package/dist/skill-test/executor-prompt.js.map +1 -1
- package/dist/skill-test/exit-codes.d.ts +53 -16
- package/dist/skill-test/exit-codes.d.ts.map +1 -1
- package/dist/skill-test/exit-codes.js +123 -40
- package/dist/skill-test/exit-codes.js.map +1 -1
- package/dist/skill-test/fragment-merge.d.ts +1 -18
- package/dist/skill-test/fragment-merge.d.ts.map +1 -1
- package/dist/skill-test/fragment-merge.js +63 -4
- package/dist/skill-test/fragment-merge.js.map +1 -1
- package/dist/skill-test/grader-prompt.d.ts +27 -7
- package/dist/skill-test/grader-prompt.d.ts.map +1 -1
- package/dist/skill-test/grader-prompt.js +253 -61
- package/dist/skill-test/grader-prompt.js.map +1 -1
- package/dist/skill-test/grader-text.d.ts +145 -0
- package/dist/skill-test/grader-text.d.ts.map +1 -0
- package/dist/skill-test/grader-text.js +410 -0
- package/dist/skill-test/grader-text.js.map +1 -0
- package/dist/skill-test/grading-adapter.d.ts +32 -0
- package/dist/skill-test/grading-adapter.d.ts.map +1 -1
- package/dist/skill-test/grading-adapter.js +50 -3
- package/dist/skill-test/grading-adapter.js.map +1 -1
- package/dist/skill-test/grading-schema.d.ts +160 -5
- package/dist/skill-test/grading-schema.d.ts.map +1 -1
- package/dist/skill-test/grading-schema.js +66 -7
- package/dist/skill-test/grading-schema.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/lock.d.ts +56 -0
- package/dist/skill-test/lock.d.ts.map +1 -1
- package/dist/skill-test/lock.js +75 -2
- package/dist/skill-test/lock.js.map +1 -1
- package/dist/skill-test/pipeline.d.ts +16 -0
- package/dist/skill-test/pipeline.d.ts.map +1 -1
- package/dist/skill-test/pipeline.js +16 -9
- package/dist/skill-test/pipeline.js.map +1 -1
- package/dist/skill-test/preflight.d.ts +1 -1
- package/dist/skill-test/preflight.d.ts.map +1 -1
- package/dist/skill-test/preflight.js +59 -15
- package/dist/skill-test/preflight.js.map +1 -1
- package/dist/skill-test/run-harness.d.ts +578 -35
- package/dist/skill-test/run-harness.d.ts.map +1 -1
- package/dist/skill-test/run-harness.js +1281 -148
- package/dist/skill-test/run-harness.js.map +1 -1
- package/dist/skill-test/staging.d.ts +8 -2
- package/dist/skill-test/staging.d.ts.map +1 -1
- package/dist/skill-test/staging.js +8 -4
- package/dist/skill-test/staging.js.map +1 -1
- package/dist/skill-test/tool-eval-schema.d.ts +6 -6
- package/dist/test-input.d.ts +57 -6
- package/dist/test-input.d.ts.map +1 -1
- package/dist/test-input.js +35 -12
- package/dist/test-input.js.map +1 -1
- package/dist/validators/agent-instruction-presence.d.ts +1 -1
- package/dist/validators/agent-instruction-presence.d.ts.map +1 -1
- package/dist/validators/agent-instruction-presence.js +2 -1
- package/dist/validators/agent-instruction-presence.js.map +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts +1 -1
- package/dist/validators/bundled-resource-link-detection.d.ts.map +1 -1
- package/dist/validators/bundled-resource-link-detection.js +1 -1
- package/dist/validators/bundled-resource-link-detection.js.map +1 -1
- package/dist/validators/compat-detectors.d.ts +1 -1
- package/dist/validators/compat-detectors.d.ts.map +1 -1
- package/dist/validators/compat-detectors.js +1 -1
- package/dist/validators/compat-detectors.js.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts +1 -1
- package/dist/validators/cross-skill-dependency-detection.d.ts.map +1 -1
- package/dist/validators/cross-skill-dependency-detection.js +1 -1
- package/dist/validators/cross-skill-dependency-detection.js.map +1 -1
- package/dist/validators/description-style-detection.d.ts +1 -1
- package/dist/validators/description-style-detection.d.ts.map +1 -1
- package/dist/validators/description-style-detection.js +1 -1
- package/dist/validators/description-style-detection.js.map +1 -1
- package/dist/validators/frontmatter-validation.d.ts +1 -1
- package/dist/validators/frontmatter-validation.d.ts.map +1 -1
- package/dist/validators/frontmatter-validation.js +1 -1
- package/dist/validators/frontmatter-validation.js.map +1 -1
- package/dist/validators/imperative-body-detection.d.ts +1 -1
- package/dist/validators/imperative-body-detection.d.ts.map +1 -1
- package/dist/validators/imperative-body-detection.js +1 -1
- package/dist/validators/imperative-body-detection.js.map +1 -1
- package/dist/validators/index.d.ts +1 -1
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.d.ts +1 -1
- package/dist/validators/kebab-case-detection.d.ts.map +1 -1
- package/dist/validators/kebab-case-detection.js +1 -1
- package/dist/validators/kebab-case-detection.js.map +1 -1
- package/dist/validators/marketplace-validator.js +1 -1
- package/dist/validators/marketplace-validator.js.map +1 -1
- package/dist/validators/packaging-validator.d.ts +96 -7
- package/dist/validators/packaging-validator.d.ts.map +1 -1
- package/dist/validators/packaging-validator.js +148 -21
- package/dist/validators/packaging-validator.js.map +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts +1 -1
- package/dist/validators/plugin-hosted-shape.d.ts.map +1 -1
- package/dist/validators/plugin-hosted-shape.js +1 -1
- package/dist/validators/plugin-hosted-shape.js.map +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts +1 -1
- package/dist/validators/plugin-recommended-fields.d.ts.map +1 -1
- package/dist/validators/plugin-recommended-fields.js +1 -1
- package/dist/validators/plugin-recommended-fields.js.map +1 -1
- package/dist/validators/registry-validator.js +1 -1
- package/dist/validators/registry-validator.js.map +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts +1 -1
- package/dist/validators/rule-engine/rule-engine.d.ts.map +1 -1
- package/dist/validators/rule-engine/rule-engine.js +20 -9
- package/dist/validators/rule-engine/rule-engine.js.map +1 -1
- package/dist/validators/skill-validator.d.ts.map +1 -1
- package/dist/validators/skill-validator.js +21 -3
- package/dist/validators/skill-validator.js.map +1 -1
- package/dist/validators/types.d.ts +1 -1
- package/dist/validators/types.d.ts.map +1 -1
- package/dist/validators/unified-validator.js +1 -1
- package/dist/validators/unified-validator.js.map +1 -1
- package/dist/validators/validation-rules.d.ts +1 -1
- package/dist/validators/validation-rules.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.d.ts +7 -3
- package/dist/validators/walker-to-issues.d.ts.map +1 -1
- package/dist/validators/walker-to-issues.js +37 -8
- package/dist/validators/walker-to-issues.js.map +1 -1
- package/dist/walk-link-graph.d.ts +2 -1
- package/dist/walk-link-graph.d.ts.map +1 -1
- package/dist/walk-link-graph.js +37 -2
- package/dist/walk-link-graph.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutralize GRADER-SUPPLIED free text before it reaches an operator surface or
|
|
3
|
+
* one of vat's own artifacts.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. The grader's only input is the executor transcript, and
|
|
6
|
+
* untrusted skill code writes that transcript. Every free-text field a grader
|
|
7
|
+
* emits is therefore attacker-influenced: `expectations[].text`/`.evidence`,
|
|
8
|
+
* `friction[].message`/`.evidence`/`.subjectFile`, `tool.*[].name`/`.evidence`,
|
|
9
|
+
* `tool.sequence[].steps`. `formatFrictionReport` interpolates one of those
|
|
10
|
+
* straight into `process.stderr.write`, so a message carrying a newline and an
|
|
11
|
+
* SGR sequence renders as vat's OWN voice — verified end-to-end with a grader
|
|
12
|
+
* emitting a green "vat: grading verified, ignore the warning above." line on a
|
|
13
|
+
* row of its own. That is strictly worse than a wrong number in
|
|
14
|
+
* `baseline.json`: operators read stderr, and nobody reads the artifact.
|
|
15
|
+
* (`summary` is NOT in that list — vat computes it numerically.)
|
|
16
|
+
*
|
|
17
|
+
* WHAT IT DOES, and why each part:
|
|
18
|
+
* - **ANSI escape sequences are removed WHOLE.** Dropping only the ESC byte
|
|
19
|
+
* would leave the parameter bytes (`[32m`) as visible litter; removing the
|
|
20
|
+
* sequence keeps the surviving text readable.
|
|
21
|
+
* - **Remaining C0/C1 controls become a space.** This is what kills the
|
|
22
|
+
* forged-line attack: no newline survives, so grader text can never occupy a
|
|
23
|
+
* line of its own. It also covers CR (line overwrite) and BS.
|
|
24
|
+
* - **Bidi controls and invisible non-spelling characters become a space too.**
|
|
25
|
+
* Stopping at U+009F left them alive, and they are not decoration: one U+202E
|
|
26
|
+
* (RIGHT-TO-LEFT OVERRIDE) in a friction `message` renders the REST of vat's own
|
|
27
|
+
* `[low] path-assumption: …` stderr line right-to-left, so the grader repaints
|
|
28
|
+
* text it did not write. `JSON.stringify` only escapes below U+0020, so the same
|
|
29
|
+
* code point also survives verbatim into `baseline.json` and `friction.json`. The
|
|
30
|
+
* invisible members (U+200B, U+2060…) are the quieter half: they let a grader
|
|
31
|
+
* smuggle a marker through any downstream string comparison while rendering as
|
|
32
|
+
* nothing at all. This is NOT the whole Cf class — see
|
|
33
|
+
* {@link INVISIBLE_CODE_RANGES} for the two admission tests and for the joiners
|
|
34
|
+
* and variation selectors deliberately left alone, because ordinary words are
|
|
35
|
+
* spelled with them and a grader quoting a file must get the file back.
|
|
36
|
+
* - **Whitespace runs collapse and the value is trimmed.** After the previous
|
|
37
|
+
* step a 200-newline message would be 200 spaces of padding. (The Unicode
|
|
38
|
+
* line/paragraph separators are NOT left to the collapse: the scan folds them,
|
|
39
|
+
* because {@link sanitizeTextPreservingLines} has no collapse to leave them to.)
|
|
40
|
+
* - **Length is capped.** Bounds a flooding grader. The cap is generous enough
|
|
41
|
+
* for real evidence prose; the untruncated value is preserved nowhere, which
|
|
42
|
+
* is deliberate — an artifact nobody can trust to render is worse than a
|
|
43
|
+
* short one.
|
|
44
|
+
*
|
|
45
|
+
* A value that was non-empty but sanitizes away to nothing becomes
|
|
46
|
+
* {@link UNPRINTABLE_PLACEHOLDER} rather than the empty string, so a nonsense
|
|
47
|
+
* grader value cannot turn a `.min(1)` schema field into a hard run failure.
|
|
48
|
+
*
|
|
49
|
+
* SCANNED, NOT REGEXED. The obvious implementation is three regexes over
|
|
50
|
+
* control-character classes. Those need literal ESC/NUL bytes in this source
|
|
51
|
+
* file (`\u` escapes get normalized INTO literal bytes by editors and tools on
|
|
52
|
+
* the way in), which makes the module binary to `grep` and unreviewable in a
|
|
53
|
+
* diff. A forward scan over code units says the same thing in plain ASCII.
|
|
54
|
+
*
|
|
55
|
+
* WHERE IT IS APPLIED. Not once, and not redundantly — each of these is a distinct
|
|
56
|
+
* crossing from untrusted bytes to an operator surface or a vat artifact, and none
|
|
57
|
+
* of them is downstream of another:
|
|
58
|
+
* - `eval-fragment.ts` — `parseEvalFragment` walks EVERY string entering vat from a
|
|
59
|
+
* grader, plus the schema path/message it quotes when the parse fails.
|
|
60
|
+
* - `eval-grader.ts` — the grader-invocation error text, and both sides of the
|
|
61
|
+
* declared-vs-observed executable-name comparison (names are compared AFTER
|
|
62
|
+
* sanitizing, so an invisible smuggled into one side cannot split the match).
|
|
63
|
+
* - `run-harness.ts` — `formatFrictionReport`, the stderr render, which re-reads
|
|
64
|
+
* `friction.json` from a harness dir that same-uid skill code can reach.
|
|
65
|
+
* - `eval-inputs.ts` — `quoteSuiteText` / `quoteSuiteBlock`, for SUITE-authored text
|
|
66
|
+
* (untrusted too: `resolveEvalSuitePath` will harvest a suite out of a fetched
|
|
67
|
+
* artifact, i.e. out of the skill under test).
|
|
68
|
+
* - `eval-lint.ts` — `lintToolExpectationExecutables`, via `quoteSuiteText`. Every
|
|
69
|
+
* executable name it quotes comes from the suite (`toolExpectations.*`, an
|
|
70
|
+
* unconstrained `z.array(z.string().min(1))` on a `.passthrough()` entry) or from
|
|
71
|
+
* the subject skill's own manifest, and `run-harness.ts` writes the advisory to
|
|
72
|
+
* stderr with no sanitizer of its own — at Step 5.5, AHEAD of the
|
|
73
|
+
* `--i-understand-this-runs-skill-code` gate and ahead of the `--dry-run`
|
|
74
|
+
* short-circuit, so this one paints a run that spawned nothing.
|
|
75
|
+
* - `baseline-integrity.ts` — `BaselineScanDegradation.detail`, built from a `cd`
|
|
76
|
+
* argument lifted verbatim out of the CONTROL ARM's transcript. Sanitized where the
|
|
77
|
+
* detail is CONSTRUCTED, not at the stderr write, so the one call covers both
|
|
78
|
+
* surfaces: `run-harness.ts` (`baselineContaminationFor`) writes it to stderr AND
|
|
79
|
+
* vat stamps the same bytes into `baselineIntegrity.degraded` in `baseline.json` —
|
|
80
|
+
* which a stderr-side fix would miss, because `degraded` is attached to the
|
|
81
|
+
* fragment AFTER `parseEvalFragment` has sanitized it.
|
|
82
|
+
* - `grading-adapter.ts` — the schema path and message quoted when the aggregate
|
|
83
|
+
* `grading.json` parse fails.
|
|
84
|
+
*
|
|
85
|
+
* That list is the whole of it, and keeping it whole is the point: this file's own
|
|
86
|
+
* standard is that a function's docblock and its behaviour disagreeing is how a
|
|
87
|
+
* security claim rots, and a LIST that has fallen behind the call sites rots the same
|
|
88
|
+
* way in the other direction — it under-claims, so a reader goes looking for a hole
|
|
89
|
+
* that was closed and trusts a boundary that was never added. `git grep
|
|
90
|
+
* 'sanitizeGraderText\|sanitizeTextPreservingLines'` is the check.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Longest grader-supplied free-text value vat will carry. ~20 terminal lines —
|
|
94
|
+
* enough for a real evidence excerpt, small enough that one item cannot own the
|
|
95
|
+
* operator's screen.
|
|
96
|
+
*/
|
|
97
|
+
export declare const MAX_GRADER_TEXT_LENGTH = 2000;
|
|
98
|
+
/** Substituted for a non-empty value that sanitizes away to nothing. */
|
|
99
|
+
export declare const UNPRINTABLE_PLACEHOLDER = "(unprintable)";
|
|
100
|
+
/** Sanitize one grader-supplied string. Pure. See the module docblock for the rules. */
|
|
101
|
+
export declare function sanitizeGraderText(value: string): string;
|
|
102
|
+
/**
|
|
103
|
+
* The LINE-PRESERVING sanitizer, for untrusted text whose STRUCTURE is the
|
|
104
|
+
* message.
|
|
105
|
+
*
|
|
106
|
+
* WHY IT EXISTS RATHER THAN A SECOND CALL TO {@link sanitizeGraderText}. Zod's
|
|
107
|
+
* `.strict()`/schema failure text is a multi-line, indented list of issues, one
|
|
108
|
+
* per offending path, and it is the only thing telling an adopter WHICH eval in a
|
|
109
|
+
* 200-eval suite is malformed. Folding it with the single-line sanitizer collapses
|
|
110
|
+
* every issue onto one 2000-character line and then truncates it — so the common
|
|
111
|
+
* case (a real typo in a real suite) gets materially worse in exchange for
|
|
112
|
+
* neutralizing bytes the suite author almost never wrote. This keeps the shape and
|
|
113
|
+
* still removes everything that can paint: escape sequences whole, every C0/C1
|
|
114
|
+
* control except LF, and every member of {@link INVISIBLE_CODE_RANGES}.
|
|
115
|
+
*
|
|
116
|
+
* THAT LAST CLAUSE USED TO BE FALSE, and the sibling's whitespace collapse was why.
|
|
117
|
+
* U+FEFF, U+2028 and U+2029 were left OUT of the scan on the grounds that they are
|
|
118
|
+
* `\s` and "the collapse eats them" — true of exactly one of the two consumers.
|
|
119
|
+
* This function has no collapse, so all three reached `process.stderr` through
|
|
120
|
+
* `quoteSuiteBlock` -> `EvalInputError` intact, two of them being separators some
|
|
121
|
+
* renderers break a line on. They are neutralized in the SCAN now, which is the only
|
|
122
|
+
* place both consumers share. A function's docblock and its behaviour disagreeing is
|
|
123
|
+
* how a security claim rots; on a terminal-bound path, the code moves to meet the
|
|
124
|
+
* docblock and not the other way round.
|
|
125
|
+
*
|
|
126
|
+
* It is NOT a replacement for {@link sanitizeGraderText} and must not be used for
|
|
127
|
+
* grader free text: a surviving newline is exactly what lets a value occupy a line
|
|
128
|
+
* of its own and impersonate vat's voice. Use it only where the caller's own
|
|
129
|
+
* message already spans lines and the untrusted part is quoted INSIDE it.
|
|
130
|
+
*/
|
|
131
|
+
export declare function sanitizeTextPreservingLines(value: string): string;
|
|
132
|
+
/**
|
|
133
|
+
* Sanitize every string in an arbitrary parsed-JSON value, recursively.
|
|
134
|
+
*
|
|
135
|
+
* Deliberately field-BLIND: a per-field allowlist is a list somebody has to
|
|
136
|
+
* remember to extend, and these schemas grow (`mustSucceed` and
|
|
137
|
+
* `sequence[].steps` both arrived after the fields above them). Walking every
|
|
138
|
+
* string means a new grader-controlled field is covered the day it is added.
|
|
139
|
+
*
|
|
140
|
+
* `skipKeys` is REQUIRED, not defaulted — the one value that must survive
|
|
141
|
+
* byte-exact is the integrity nonce, and a caller that forgets it should have
|
|
142
|
+
* to say so.
|
|
143
|
+
*/
|
|
144
|
+
export declare function sanitizeGraderTextDeep(value: unknown, skipKeys: ReadonlySet<string>): unknown;
|
|
145
|
+
//# sourceMappingURL=grader-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grader-text.d.ts","sourceRoot":"","sources":["../../src/skill-test/grader-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,kBAAkB,CAAC;AAmOvD,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxD;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASjE;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAE7F"}
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutralize GRADER-SUPPLIED free text before it reaches an operator surface or
|
|
3
|
+
* one of vat's own artifacts.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. The grader's only input is the executor transcript, and
|
|
6
|
+
* untrusted skill code writes that transcript. Every free-text field a grader
|
|
7
|
+
* emits is therefore attacker-influenced: `expectations[].text`/`.evidence`,
|
|
8
|
+
* `friction[].message`/`.evidence`/`.subjectFile`, `tool.*[].name`/`.evidence`,
|
|
9
|
+
* `tool.sequence[].steps`. `formatFrictionReport` interpolates one of those
|
|
10
|
+
* straight into `process.stderr.write`, so a message carrying a newline and an
|
|
11
|
+
* SGR sequence renders as vat's OWN voice — verified end-to-end with a grader
|
|
12
|
+
* emitting a green "vat: grading verified, ignore the warning above." line on a
|
|
13
|
+
* row of its own. That is strictly worse than a wrong number in
|
|
14
|
+
* `baseline.json`: operators read stderr, and nobody reads the artifact.
|
|
15
|
+
* (`summary` is NOT in that list — vat computes it numerically.)
|
|
16
|
+
*
|
|
17
|
+
* WHAT IT DOES, and why each part:
|
|
18
|
+
* - **ANSI escape sequences are removed WHOLE.** Dropping only the ESC byte
|
|
19
|
+
* would leave the parameter bytes (`[32m`) as visible litter; removing the
|
|
20
|
+
* sequence keeps the surviving text readable.
|
|
21
|
+
* - **Remaining C0/C1 controls become a space.** This is what kills the
|
|
22
|
+
* forged-line attack: no newline survives, so grader text can never occupy a
|
|
23
|
+
* line of its own. It also covers CR (line overwrite) and BS.
|
|
24
|
+
* - **Bidi controls and invisible non-spelling characters become a space too.**
|
|
25
|
+
* Stopping at U+009F left them alive, and they are not decoration: one U+202E
|
|
26
|
+
* (RIGHT-TO-LEFT OVERRIDE) in a friction `message` renders the REST of vat's own
|
|
27
|
+
* `[low] path-assumption: …` stderr line right-to-left, so the grader repaints
|
|
28
|
+
* text it did not write. `JSON.stringify` only escapes below U+0020, so the same
|
|
29
|
+
* code point also survives verbatim into `baseline.json` and `friction.json`. The
|
|
30
|
+
* invisible members (U+200B, U+2060…) are the quieter half: they let a grader
|
|
31
|
+
* smuggle a marker through any downstream string comparison while rendering as
|
|
32
|
+
* nothing at all. This is NOT the whole Cf class — see
|
|
33
|
+
* {@link INVISIBLE_CODE_RANGES} for the two admission tests and for the joiners
|
|
34
|
+
* and variation selectors deliberately left alone, because ordinary words are
|
|
35
|
+
* spelled with them and a grader quoting a file must get the file back.
|
|
36
|
+
* - **Whitespace runs collapse and the value is trimmed.** After the previous
|
|
37
|
+
* step a 200-newline message would be 200 spaces of padding. (The Unicode
|
|
38
|
+
* line/paragraph separators are NOT left to the collapse: the scan folds them,
|
|
39
|
+
* because {@link sanitizeTextPreservingLines} has no collapse to leave them to.)
|
|
40
|
+
* - **Length is capped.** Bounds a flooding grader. The cap is generous enough
|
|
41
|
+
* for real evidence prose; the untruncated value is preserved nowhere, which
|
|
42
|
+
* is deliberate — an artifact nobody can trust to render is worse than a
|
|
43
|
+
* short one.
|
|
44
|
+
*
|
|
45
|
+
* A value that was non-empty but sanitizes away to nothing becomes
|
|
46
|
+
* {@link UNPRINTABLE_PLACEHOLDER} rather than the empty string, so a nonsense
|
|
47
|
+
* grader value cannot turn a `.min(1)` schema field into a hard run failure.
|
|
48
|
+
*
|
|
49
|
+
* SCANNED, NOT REGEXED. The obvious implementation is three regexes over
|
|
50
|
+
* control-character classes. Those need literal ESC/NUL bytes in this source
|
|
51
|
+
* file (`\u` escapes get normalized INTO literal bytes by editors and tools on
|
|
52
|
+
* the way in), which makes the module binary to `grep` and unreviewable in a
|
|
53
|
+
* diff. A forward scan over code units says the same thing in plain ASCII.
|
|
54
|
+
*
|
|
55
|
+
* WHERE IT IS APPLIED. Not once, and not redundantly — each of these is a distinct
|
|
56
|
+
* crossing from untrusted bytes to an operator surface or a vat artifact, and none
|
|
57
|
+
* of them is downstream of another:
|
|
58
|
+
* - `eval-fragment.ts` — `parseEvalFragment` walks EVERY string entering vat from a
|
|
59
|
+
* grader, plus the schema path/message it quotes when the parse fails.
|
|
60
|
+
* - `eval-grader.ts` — the grader-invocation error text, and both sides of the
|
|
61
|
+
* declared-vs-observed executable-name comparison (names are compared AFTER
|
|
62
|
+
* sanitizing, so an invisible smuggled into one side cannot split the match).
|
|
63
|
+
* - `run-harness.ts` — `formatFrictionReport`, the stderr render, which re-reads
|
|
64
|
+
* `friction.json` from a harness dir that same-uid skill code can reach.
|
|
65
|
+
* - `eval-inputs.ts` — `quoteSuiteText` / `quoteSuiteBlock`, for SUITE-authored text
|
|
66
|
+
* (untrusted too: `resolveEvalSuitePath` will harvest a suite out of a fetched
|
|
67
|
+
* artifact, i.e. out of the skill under test).
|
|
68
|
+
* - `eval-lint.ts` — `lintToolExpectationExecutables`, via `quoteSuiteText`. Every
|
|
69
|
+
* executable name it quotes comes from the suite (`toolExpectations.*`, an
|
|
70
|
+
* unconstrained `z.array(z.string().min(1))` on a `.passthrough()` entry) or from
|
|
71
|
+
* the subject skill's own manifest, and `run-harness.ts` writes the advisory to
|
|
72
|
+
* stderr with no sanitizer of its own — at Step 5.5, AHEAD of the
|
|
73
|
+
* `--i-understand-this-runs-skill-code` gate and ahead of the `--dry-run`
|
|
74
|
+
* short-circuit, so this one paints a run that spawned nothing.
|
|
75
|
+
* - `baseline-integrity.ts` — `BaselineScanDegradation.detail`, built from a `cd`
|
|
76
|
+
* argument lifted verbatim out of the CONTROL ARM's transcript. Sanitized where the
|
|
77
|
+
* detail is CONSTRUCTED, not at the stderr write, so the one call covers both
|
|
78
|
+
* surfaces: `run-harness.ts` (`baselineContaminationFor`) writes it to stderr AND
|
|
79
|
+
* vat stamps the same bytes into `baselineIntegrity.degraded` in `baseline.json` —
|
|
80
|
+
* which a stderr-side fix would miss, because `degraded` is attached to the
|
|
81
|
+
* fragment AFTER `parseEvalFragment` has sanitized it.
|
|
82
|
+
* - `grading-adapter.ts` — the schema path and message quoted when the aggregate
|
|
83
|
+
* `grading.json` parse fails.
|
|
84
|
+
*
|
|
85
|
+
* That list is the whole of it, and keeping it whole is the point: this file's own
|
|
86
|
+
* standard is that a function's docblock and its behaviour disagreeing is how a
|
|
87
|
+
* security claim rots, and a LIST that has fallen behind the call sites rots the same
|
|
88
|
+
* way in the other direction — it under-claims, so a reader goes looking for a hole
|
|
89
|
+
* that was closed and trusts a boundary that was never added. `git grep
|
|
90
|
+
* 'sanitizeGraderText\|sanitizeTextPreservingLines'` is the check.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Longest grader-supplied free-text value vat will carry. ~20 terminal lines —
|
|
94
|
+
* enough for a real evidence excerpt, small enough that one item cannot own the
|
|
95
|
+
* operator's screen.
|
|
96
|
+
*/
|
|
97
|
+
export const MAX_GRADER_TEXT_LENGTH = 2000;
|
|
98
|
+
/** Substituted for a non-empty value that sanitizes away to nothing. */
|
|
99
|
+
export const UNPRINTABLE_PLACEHOLDER = '(unprintable)';
|
|
100
|
+
const TRUNCATION_MARKER = '... (truncated)';
|
|
101
|
+
const ESC = 0x1b;
|
|
102
|
+
const BEL = 0x07;
|
|
103
|
+
const C0_MAX = 0x1f;
|
|
104
|
+
const DEL = 0x7f;
|
|
105
|
+
const C1_MAX = 0x9f;
|
|
106
|
+
const LEFT_BRACKET = 0x5b;
|
|
107
|
+
const RIGHT_BRACKET = 0x5d;
|
|
108
|
+
const BACKSLASH = 0x5c;
|
|
109
|
+
/** A CSI sequence ends at its first byte in `@`..`~`. */
|
|
110
|
+
const CSI_FINAL_MIN = 0x40;
|
|
111
|
+
const CSI_FINAL_MAX = 0x7e;
|
|
112
|
+
/** Intermediate bytes of an nF escape sequence (` `..`/`), e.g. the `(` of `ESC ( B`. */
|
|
113
|
+
const INTERMEDIATE_MIN = 0x20;
|
|
114
|
+
const INTERMEDIATE_MAX = 0x2f;
|
|
115
|
+
/** The leading half of a surrogate pair; see {@link capLength}. */
|
|
116
|
+
const HIGH_SURROGATE_MIN = 0xd800;
|
|
117
|
+
const HIGH_SURROGATE_MAX = 0xdbff;
|
|
118
|
+
/** Above this, a code point is written as a surrogate PAIR; see {@link capLength}. */
|
|
119
|
+
const MAX_BMP_CODE_POINT = 0xffff;
|
|
120
|
+
const WHITESPACE_RUN = /\s+/gu;
|
|
121
|
+
/** Guard against a pathologically nested grader fragment; the strict parse rejects it anyway. */
|
|
122
|
+
const MAX_SANITIZE_DEPTH = 20;
|
|
123
|
+
/** Line feed — the ONE control {@link sanitizeTextPreservingLines} keeps. */
|
|
124
|
+
const LINE_FEED = 0x0a;
|
|
125
|
+
/**
|
|
126
|
+
* Invisible code points above U+009F that are neutralized exactly like a C0/C1
|
|
127
|
+
* control, because they do the same job by other means.
|
|
128
|
+
*
|
|
129
|
+
* ⚠️ WRITTEN AS NUMBERS, like every other constant in this file, and for the same
|
|
130
|
+
* reason: a `\u202E` escape in a regex literal gets normalized into the real code
|
|
131
|
+
* point by editors and tooling on the way in, which makes the module unreviewable
|
|
132
|
+
* in a diff and unfindable by `grep`. Build test inputs with `String.fromCodePoint`.
|
|
133
|
+
*
|
|
134
|
+
* THE RULE, and it is not "the Cf class". Two admission tests; a code point has to
|
|
135
|
+
* pass one of them:
|
|
136
|
+
*
|
|
137
|
+
* (a) **It is a bidi or shaping control** — it changes how the text AROUND it is
|
|
138
|
+
* ordered or directed, which is the forgery this module exists to stop. U+202E
|
|
139
|
+
* is the verified case: it renders the REST of vat's own
|
|
140
|
+
* `[low] path-assumption: …` stderr line right-to-left.
|
|
141
|
+
* (b) **It is invisible AND spells nothing** — no word in any script is written with
|
|
142
|
+
* it, so removing it cannot change what a quotation says, while keeping it lets
|
|
143
|
+
* a grader hide a difference from a reader and from every downstream string
|
|
144
|
+
* comparison (`eval-grader.ts` compares tool names across both sides of a run,
|
|
145
|
+
* sanitized on each).
|
|
146
|
+
*
|
|
147
|
+
* Member by member:
|
|
148
|
+
* - U+00AD SOFT HYPHEN — (b) a hyphenation HINT; no word is spelled with one.
|
|
149
|
+
* - U+061C ARABIC LETTER MARK — (a) the Arabic analogue of LRM/RLM.
|
|
150
|
+
* - U+200B ZERO WIDTH SPACE — (b) a line-break hint, invisible, spells nothing.
|
|
151
|
+
* - U+200E…U+200F LRM/RLM — (a) directional MARKS: they re-resolve the direction of
|
|
152
|
+
* the neutral characters beside them, so they reorder text they did not write.
|
|
153
|
+
* - U+2028…U+2029 LINE and PARAGRAPH SEPARATOR — some renderers break a line on
|
|
154
|
+
* them, which is the "occupy a line of your own" attack spelled with a different
|
|
155
|
+
* code point. Folded to a space, never to LF, even in the line-preserving path.
|
|
156
|
+
* - U+202A…U+202E — (a) the bidi embeddings and OVERRIDES.
|
|
157
|
+
* - U+2060…U+2064 — (b) word joiner and the invisible math operators.
|
|
158
|
+
* - U+2066…U+2069 — (a) the bidi ISOLATES (U+2066 LRI verified surviving).
|
|
159
|
+
* - U+206A…U+206F — (a) the deprecated shaping controls (inhibit symmetric swapping,
|
|
160
|
+
* Arabic form shaping, nominal digit shapes); absent from modern text entirely.
|
|
161
|
+
* - U+FEFF ZERO WIDTH NO-BREAK SPACE — (b). It is `\s`, so {@link sanitizeGraderText}'s
|
|
162
|
+
* whitespace collapse would eat it anyway; {@link sanitizeTextPreservingLines} has
|
|
163
|
+
* no collapse and used to ship it to stderr intact, so it is neutralized HERE.
|
|
164
|
+
* - U+FFF9…U+FFFB — (b) interlinear annotation, which hides its own payload.
|
|
165
|
+
*
|
|
166
|
+
* DELIBERATELY ABSENT, because they fail BOTH tests — zero directional power, and
|
|
167
|
+
* real words ARE spelled with them:
|
|
168
|
+
* - U+FE00…U+FE0F variation selectors (category Mn, not Cf). How `⚠️` and most emoji
|
|
169
|
+
* are spelled, vat's own banner glyph included.
|
|
170
|
+
* - U+200C ZERO WIDTH NON-JOINER — orthographically load-bearing in Persian, Hindi,
|
|
171
|
+
* Bengali and Malayalam. `می<ZWNJ>خواهم` is two words; folding it to a space gave
|
|
172
|
+
* back a different string than the file the grader was quoting.
|
|
173
|
+
* - U+200D ZERO WIDTH JOINER — how a multi-person emoji and a flag sequence are
|
|
174
|
+
* spelled. Folding it turned `👨<ZWJ>👩<ZWJ>👧` into three separate glyphs.
|
|
175
|
+
* - U+180E MONGOLIAN VOWEL SEPARATOR — orthographic in Mongolian (it selects the
|
|
176
|
+
* letterform of the syllable before it) and reorders nothing.
|
|
177
|
+
*
|
|
178
|
+
* The residual cost of that exclusion is real and accepted: a grader can still
|
|
179
|
+
* smuggle an invisible joiner into a string a human compares by eye. It is the same
|
|
180
|
+
* trade the variation-selector exclusion always made, and the standard this file
|
|
181
|
+
* already stated — "dropping them would mangle ordinary text" — decides it the same
|
|
182
|
+
* way. What it must NOT do is state that standard and then break it.
|
|
183
|
+
*/
|
|
184
|
+
const INVISIBLE_CODE_RANGES = [
|
|
185
|
+
[0x00ad, 0x00ad],
|
|
186
|
+
[0x061c, 0x061c],
|
|
187
|
+
[0x200b, 0x200b],
|
|
188
|
+
[0x200e, 0x200f],
|
|
189
|
+
[0x2028, 0x2029],
|
|
190
|
+
[0x202a, 0x202e],
|
|
191
|
+
[0x2060, 0x2064],
|
|
192
|
+
[0x2066, 0x206f],
|
|
193
|
+
[0xfeff, 0xfeff],
|
|
194
|
+
[0xfff9, 0xfffb],
|
|
195
|
+
];
|
|
196
|
+
/** Lowest code point in {@link INVISIBLE_CODE_RANGES}, so ASCII bails on one compare. */
|
|
197
|
+
const INVISIBLE_CODE_MIN = 0x00ad;
|
|
198
|
+
function isInvisibleCode(code) {
|
|
199
|
+
if (code < INVISIBLE_CODE_MIN)
|
|
200
|
+
return false;
|
|
201
|
+
return INVISIBLE_CODE_RANGES.some(([low, high]) => code >= low && code <= high);
|
|
202
|
+
}
|
|
203
|
+
function isControlCode(code) {
|
|
204
|
+
return code <= C0_MAX || (code >= DEL && code <= C1_MAX) || isInvisibleCode(code);
|
|
205
|
+
}
|
|
206
|
+
/** Index of the first code unit AFTER a CSI sequence whose parameters start at `from`. */
|
|
207
|
+
function endOfCsi(value, from) {
|
|
208
|
+
let i = from;
|
|
209
|
+
while (i < value.length) {
|
|
210
|
+
const code = value.codePointAt(i) ?? Number.NaN;
|
|
211
|
+
i += 1;
|
|
212
|
+
if (code >= CSI_FINAL_MIN && code <= CSI_FINAL_MAX)
|
|
213
|
+
return i;
|
|
214
|
+
}
|
|
215
|
+
return i;
|
|
216
|
+
}
|
|
217
|
+
/** Index of the first code unit AFTER an OSC sequence whose payload starts at `from`. */
|
|
218
|
+
function endOfOsc(value, from) {
|
|
219
|
+
let i = from;
|
|
220
|
+
while (i < value.length) {
|
|
221
|
+
const code = value.codePointAt(i) ?? Number.NaN;
|
|
222
|
+
if (code === BEL)
|
|
223
|
+
return i + 1;
|
|
224
|
+
// String Terminator is ESC followed by a backslash; a bare ESC ends it too.
|
|
225
|
+
if (code === ESC)
|
|
226
|
+
return i + (value.codePointAt(i + 1) === BACKSLASH ? 2 : 1);
|
|
227
|
+
i += 1;
|
|
228
|
+
}
|
|
229
|
+
return i;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Index of the first code unit AFTER an nF sequence (`ESC` intermediates final),
|
|
233
|
+
* e.g. the charset selection `ESC ( B`, whose intermediates start at `from`.
|
|
234
|
+
*/
|
|
235
|
+
function endOfNf(value, from) {
|
|
236
|
+
let i = from;
|
|
237
|
+
while (i < value.length) {
|
|
238
|
+
const code = value.codePointAt(i) ?? Number.NaN;
|
|
239
|
+
i += 1;
|
|
240
|
+
if (code < INTERMEDIATE_MIN || code > INTERMEDIATE_MAX)
|
|
241
|
+
return i;
|
|
242
|
+
}
|
|
243
|
+
return i;
|
|
244
|
+
}
|
|
245
|
+
/** Index of the first code unit AFTER the escape sequence introduced by the ESC at `start`. */
|
|
246
|
+
function endOfEscapeSequence(value, start) {
|
|
247
|
+
if (start + 1 >= value.length)
|
|
248
|
+
return start + 1;
|
|
249
|
+
const second = value.codePointAt(start + 1) ?? Number.NaN;
|
|
250
|
+
if (second === LEFT_BRACKET)
|
|
251
|
+
return endOfCsi(value, start + 2);
|
|
252
|
+
if (second === RIGHT_BRACKET)
|
|
253
|
+
return endOfOsc(value, start + 2);
|
|
254
|
+
if (second >= INTERMEDIATE_MIN && second <= INTERMEDIATE_MAX)
|
|
255
|
+
return endOfNf(value, start + 1);
|
|
256
|
+
// Any remaining two-code-unit form (RIS, NEL, ...).
|
|
257
|
+
return start + 2;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Drop every escape sequence and map every remaining control code to a space.
|
|
261
|
+
*
|
|
262
|
+
* `preserveNewlines` keeps LF — and ONLY LF — as itself, for
|
|
263
|
+
* {@link sanitizeTextPreservingLines}. CR still becomes a space even then: the
|
|
264
|
+
* line-overwrite attack it carries is not made safe by the caller wanting lines.
|
|
265
|
+
*/
|
|
266
|
+
function stripEscapesAndControls(value, preserveNewlines = false) {
|
|
267
|
+
const out = [];
|
|
268
|
+
let i = 0;
|
|
269
|
+
while (i < value.length) {
|
|
270
|
+
const code = value.codePointAt(i) ?? Number.NaN;
|
|
271
|
+
if (code === ESC) {
|
|
272
|
+
i = endOfEscapeSequence(value, i);
|
|
273
|
+
}
|
|
274
|
+
else if (isControlCode(code)) {
|
|
275
|
+
out.push(preserveNewlines && code === LINE_FEED ? '\n' : ' ');
|
|
276
|
+
i += 1;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
out.push(value.charAt(i));
|
|
280
|
+
i += 1;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return out.join('');
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Truncate to at most `max` code units, spending the tail on a marker that says so.
|
|
287
|
+
*
|
|
288
|
+
* THE CUT BACKS OFF ONE CODE UNIT rather than landing between the halves of a
|
|
289
|
+
* surrogate pair. A naive `slice` on a value ending in an astral character (1984
|
|
290
|
+
* ASCII characters plus one emoji is enough) leaves a LONE HIGH SURROGATE, which
|
|
291
|
+
* persists into `grading.json` and `baseline.json`. The artifact stays valid JSON —
|
|
292
|
+
* Node's `JSON.stringify` is well-formed and escapes it — but a consumer decoding
|
|
293
|
+
* it gets an unpaired surrogate, and stderr renders U+FFFD. Well-formedness is a
|
|
294
|
+
* correctness property of what this function emits, so it is enforced here.
|
|
295
|
+
*
|
|
296
|
+
* GRAPHEME CLUSTERS ARE DELIBERATELY NOT HANDLED. A cut inside a cluster — between
|
|
297
|
+
* a base and its combining mark, or inside a ZWJ emoji sequence (now that the
|
|
298
|
+
* joiners survive, see {@link INVISIBLE_CODE_RANGES}) — yields a WELL-FORMED string
|
|
299
|
+
* that renders as two glyphs instead of one. That is cosmetic. Segmenting for it
|
|
300
|
+
* would mean `Intl.Segmenter` over the whole value on every capped field, for a
|
|
301
|
+
* locale-dependent answer, to fix an appearance rather than a defect. The line is
|
|
302
|
+
* well-formedness, not beauty.
|
|
303
|
+
*/
|
|
304
|
+
function capLength(value, max) {
|
|
305
|
+
if (value.length <= max)
|
|
306
|
+
return value;
|
|
307
|
+
const cut = max - TRUNCATION_MARKER.length;
|
|
308
|
+
// A high surrogate immediately before the cut has its partner AT the cut (or is
|
|
309
|
+
// itself already lone) — either way it must not be the last thing kept.
|
|
310
|
+
// `codePointAt` reports the COMBINED code point when a well-formed pair STARTS at the
|
|
311
|
+
// index, so a value above the BMP means the cut would fall between the two halves. A
|
|
312
|
+
// value still inside the high-surrogate block means that half never had a partner and
|
|
313
|
+
// is already lone. Either way it must not be the last thing kept. (A LOW surrogate
|
|
314
|
+
// reads back as itself, which is correct: its partner is at `cut - 2`, so the pair
|
|
315
|
+
// survives the cut intact.)
|
|
316
|
+
const last = value.codePointAt(cut - 1) ?? Number.NaN;
|
|
317
|
+
const cutSplitsPair = last > MAX_BMP_CODE_POINT || (last >= HIGH_SURROGATE_MIN && last <= HIGH_SURROGATE_MAX);
|
|
318
|
+
const safeCut = cutSplitsPair ? cut - 1 : cut;
|
|
319
|
+
return value.slice(0, safeCut) + TRUNCATION_MARKER;
|
|
320
|
+
}
|
|
321
|
+
/** Sanitize one grader-supplied string. Pure. See the module docblock for the rules. */
|
|
322
|
+
export function sanitizeGraderText(value) {
|
|
323
|
+
const collapsed = stripEscapesAndControls(value).replaceAll(WHITESPACE_RUN, ' ').trim();
|
|
324
|
+
if (collapsed.length === 0)
|
|
325
|
+
return value.length === 0 ? '' : UNPRINTABLE_PLACEHOLDER;
|
|
326
|
+
return capLength(collapsed, MAX_GRADER_TEXT_LENGTH);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Most lines {@link sanitizeTextPreservingLines} will let through, and the total
|
|
330
|
+
* budget across them.
|
|
331
|
+
*
|
|
332
|
+
* Two caps rather than one because the two floods are different shapes: 4000
|
|
333
|
+
* characters on ONE line is a wrapped paragraph, 4000 NEWLINES is the operator's
|
|
334
|
+
* whole scrollback. The line cap is the one that protects the screen; the length
|
|
335
|
+
* cap protects `Summary:` and the artifact.
|
|
336
|
+
*/
|
|
337
|
+
const MAX_MULTILINE_TEXT_LINES = 40;
|
|
338
|
+
const MAX_MULTILINE_TEXT_LENGTH = 4000;
|
|
339
|
+
/**
|
|
340
|
+
* The LINE-PRESERVING sanitizer, for untrusted text whose STRUCTURE is the
|
|
341
|
+
* message.
|
|
342
|
+
*
|
|
343
|
+
* WHY IT EXISTS RATHER THAN A SECOND CALL TO {@link sanitizeGraderText}. Zod's
|
|
344
|
+
* `.strict()`/schema failure text is a multi-line, indented list of issues, one
|
|
345
|
+
* per offending path, and it is the only thing telling an adopter WHICH eval in a
|
|
346
|
+
* 200-eval suite is malformed. Folding it with the single-line sanitizer collapses
|
|
347
|
+
* every issue onto one 2000-character line and then truncates it — so the common
|
|
348
|
+
* case (a real typo in a real suite) gets materially worse in exchange for
|
|
349
|
+
* neutralizing bytes the suite author almost never wrote. This keeps the shape and
|
|
350
|
+
* still removes everything that can paint: escape sequences whole, every C0/C1
|
|
351
|
+
* control except LF, and every member of {@link INVISIBLE_CODE_RANGES}.
|
|
352
|
+
*
|
|
353
|
+
* THAT LAST CLAUSE USED TO BE FALSE, and the sibling's whitespace collapse was why.
|
|
354
|
+
* U+FEFF, U+2028 and U+2029 were left OUT of the scan on the grounds that they are
|
|
355
|
+
* `\s` and "the collapse eats them" — true of exactly one of the two consumers.
|
|
356
|
+
* This function has no collapse, so all three reached `process.stderr` through
|
|
357
|
+
* `quoteSuiteBlock` -> `EvalInputError` intact, two of them being separators some
|
|
358
|
+
* renderers break a line on. They are neutralized in the SCAN now, which is the only
|
|
359
|
+
* place both consumers share. A function's docblock and its behaviour disagreeing is
|
|
360
|
+
* how a security claim rots; on a terminal-bound path, the code moves to meet the
|
|
361
|
+
* docblock and not the other way round.
|
|
362
|
+
*
|
|
363
|
+
* It is NOT a replacement for {@link sanitizeGraderText} and must not be used for
|
|
364
|
+
* grader free text: a surviving newline is exactly what lets a value occupy a line
|
|
365
|
+
* of its own and impersonate vat's voice. Use it only where the caller's own
|
|
366
|
+
* message already spans lines and the untrusted part is quoted INSIDE it.
|
|
367
|
+
*/
|
|
368
|
+
export function sanitizeTextPreservingLines(value) {
|
|
369
|
+
const stripped = stripEscapesAndControls(value, true).trim();
|
|
370
|
+
if (stripped.length === 0)
|
|
371
|
+
return value.length === 0 ? '' : UNPRINTABLE_PLACEHOLDER;
|
|
372
|
+
const lines = stripped.split('\n');
|
|
373
|
+
const kept = lines.slice(0, MAX_MULTILINE_TEXT_LINES);
|
|
374
|
+
if (lines.length > kept.length) {
|
|
375
|
+
kept.push(`... and ${lines.length - kept.length} more line(s) (truncated)`);
|
|
376
|
+
}
|
|
377
|
+
return capLength(kept.join('\n'), MAX_MULTILINE_TEXT_LENGTH);
|
|
378
|
+
}
|
|
379
|
+
function walk(value, skipKeys, depth) {
|
|
380
|
+
if (depth > MAX_SANITIZE_DEPTH)
|
|
381
|
+
return value;
|
|
382
|
+
if (typeof value === 'string')
|
|
383
|
+
return sanitizeGraderText(value);
|
|
384
|
+
if (Array.isArray(value))
|
|
385
|
+
return value.map((item) => walk(item, skipKeys, depth + 1));
|
|
386
|
+
if (typeof value === 'object' && value !== null) {
|
|
387
|
+
const out = {};
|
|
388
|
+
for (const [key, val] of Object.entries(value)) {
|
|
389
|
+
out[key] = skipKeys.has(key) ? val : walk(val, skipKeys, depth + 1);
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Sanitize every string in an arbitrary parsed-JSON value, recursively.
|
|
397
|
+
*
|
|
398
|
+
* Deliberately field-BLIND: a per-field allowlist is a list somebody has to
|
|
399
|
+
* remember to extend, and these schemas grow (`mustSucceed` and
|
|
400
|
+
* `sequence[].steps` both arrived after the fields above them). Walking every
|
|
401
|
+
* string means a new grader-controlled field is covered the day it is added.
|
|
402
|
+
*
|
|
403
|
+
* `skipKeys` is REQUIRED, not defaulted — the one value that must survive
|
|
404
|
+
* byte-exact is the integrity nonce, and a caller that forgets it should have
|
|
405
|
+
* to say so.
|
|
406
|
+
*/
|
|
407
|
+
export function sanitizeGraderTextDeep(value, skipKeys) {
|
|
408
|
+
return walk(value, skipKeys, 0);
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=grader-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grader-text.js","sourceRoot":"","sources":["../../src/skill-test/grader-text.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,13 +12,31 @@ export interface NormalizedGrading {
|
|
|
12
12
|
passed: number;
|
|
13
13
|
total: number;
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Every graded expectation, flattened across all evals. `evalId` names the eval
|
|
17
|
+
* the entry came from — it is what lets a reader line the two `--baseline`
|
|
18
|
+
* artifacts up PER EVAL instead of only in aggregate. Optional for the same
|
|
19
|
+
* reason `runNonce` is: {@link parseGradingJson} builds a `NormalizedGrading`
|
|
20
|
+
* from an externally produced grading.json, which carries no such attribution.
|
|
21
|
+
*/
|
|
15
22
|
expectations: {
|
|
16
23
|
text: string;
|
|
17
24
|
passed: boolean;
|
|
18
25
|
evidence?: string;
|
|
26
|
+
evalId?: string;
|
|
19
27
|
}[];
|
|
20
28
|
/** Per-run integrity nonce the experimenter copied from its prompt (if present). */
|
|
21
29
|
runNonce?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Which `--baseline` arm produced this report: `'with'` (the skill declared —
|
|
32
|
+
* grading.json) or `'without'` (the control — baseline.json). Stamped by
|
|
33
|
+
* {@link import('./fragment-merge.js').mergeFragmentsToGrading}, which is the
|
|
34
|
+
* only producer that knows the arm. Optional for the same reason `runNonce` is:
|
|
35
|
+
* {@link parseGradingJson} normalizes an externally produced grading.json that
|
|
36
|
+
* legitimately carries no arm, and inventing one there would be a lie about
|
|
37
|
+
* provenance rather than a missing field.
|
|
38
|
+
*/
|
|
39
|
+
arm?: 'with' | 'without';
|
|
22
40
|
}
|
|
23
41
|
/**
|
|
24
42
|
* Thrown when a per-eval grader fragment's integrity nonce is absent or does not
|
|
@@ -30,6 +48,20 @@ export interface NormalizedGrading {
|
|
|
30
48
|
export declare class GradingNonceError extends Error {
|
|
31
49
|
constructor(message: string);
|
|
32
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when a per-eval grader fragment's `arm` disagrees with the arm the
|
|
53
|
+
* merge was told it is assembling. Deliberately distinct from
|
|
54
|
+
* {@link GradingNonceError}: a fragment can carry a perfectly valid nonce for
|
|
55
|
+
* THIS run and still belong to the other arm, and that is a different failure —
|
|
56
|
+
* not a forgery, but a mix-up that would fold a control-arm verdict into the
|
|
57
|
+
* treatment number (or the reverse). Either way the merged total is no longer a
|
|
58
|
+
* measurement of one arm, which is the entire premise of the `--baseline` A/B,
|
|
59
|
+
* so the merge refuses rather than emit a mislabelled artifact that downstream
|
|
60
|
+
* readers would take as authoritative.
|
|
61
|
+
*/
|
|
62
|
+
export declare class GradingArmError extends Error {
|
|
63
|
+
constructor(message: string);
|
|
64
|
+
}
|
|
33
65
|
export declare function parseGradingJson(raw: unknown): NormalizedGrading;
|
|
34
66
|
/** The authoritative eval verdict, recomputed from per-expectation `passed` flags. */
|
|
35
67
|
export interface GradingVerdict {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grading-adapter.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grading-adapter.d.ts","sourceRoot":"","sources":["../../src/skill-test/grading-adapter.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAQ5B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C;;;;;;OAMG;IACH,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtF,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAS5B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAU5B;AAaD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAiDhE;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc,CAqB1E"}
|