agent-sanitizer 2.67.1 → 2.68.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.
@@ -170,10 +170,15 @@ export function startHookTimer(now?: () => number, cpuNow?: () => number): {
170
170
  * sanitizer computing, and with `context.redactorMs` too it names the window the
171
171
  * time went into ({@link attributeWait}), including `context.hostMs`'s host
172
172
  * extensions — a caller that measured the first two but has no extensions to
173
- * charge reports that window as zero, which is a measurement and not a shrug. Without them the line says that it cannot
174
- * tell, rather than asserting an attribution nothing measured: a wall-clock
175
- * overrun on a loaded host is the common case, and blaming it on the sanitizer
176
- * sends the operator hunting a per-call cost that does not exist.
173
+ * charge reports that window as zero, which is a measurement and not a shrug.
174
+ * Such a caller is reported only when `cpuMs + redactorMs + hostMs` (an absent
175
+ * `hostMs` counting as 0) exceeds `thresholdMs`: a wall-clock overrun whose
176
+ * owned share is within the budget is the machine's and returns null, so the
177
+ * operator is never asked to report a cost the owner did not spend. Without the
178
+ * measurements the line says that it cannot tell, rather than asserting an
179
+ * attribution nothing measured: a wall-clock overrun on a loaded host is the
180
+ * common case, and blaming it on the sanitizer sends the operator hunting a
181
+ * per-call cost that does not exist.
177
182
  *
178
183
  * A CPU figure alone cannot pick a cause, so with only that the clause names
179
184
  * candidates and commits to none. A hook that blocks on a dead socket inside a
@@ -55,9 +55,9 @@ export function classifySocket(socketPath: string, deps?: {
55
55
  /**
56
56
  * Open one connection, send `request`, resolve with the parsed response object
57
57
  * (or null). Rejects on connect failure, a malformed/oversize/short frame, or an
58
- * {error} response — every one of which the caller turns into a fail-closed. A
58
+ * {error} response — every one of which the caller treats as a fault. A
59
59
  * socket present but not owned by us, and a request the protocol does not
60
- * accept, fail closed WITHOUT dialing (the error carries no errno, so
60
+ * accept, throw WITHOUT dialing (the error carries no errno, so
61
61
  * isRespawnable is false): we never dial into a squat, and an oversize request
62
62
  * is not mistaken for a dead daemon worth respawning.
63
63
  * @param {string} socketPath
@@ -105,7 +105,7 @@ export function waitForSocket(socketPath: string, { deadlineMs, stepMs, abandon
105
105
  /**
106
106
  * Redact `text` via the daemon. Returns the response object (`{text, found}` for
107
107
  * plain, `{text, pairs, found}` / `{unmappable}` for map) or null when nothing was
108
- * redacted (plain mode). Throws to fail closed when the text cannot be vetted.
108
+ * redacted (plain mode). Throws when the text cannot be vetted.
109
109
  *
110
110
  * `connect`/`spawn`/`waitForSocket` are injectable seams (default to the real
111
111
  * implementations) so callers can stub the daemon in-process. `deadline` is the
@@ -1,26 +1,20 @@
1
- /**
2
- * Regex matching `value` tolerating invisible chars spliced between its
3
- * characters (mirrors the engine's env-value regex). Code-point split so
4
- * an astral character is escaped whole, not as two surrogate halves — and the
5
- * `u` flag, which the astral `\u{…}` class members in {@link ENV_INVIS_RUN}
6
- * require.
7
- * Memoized per distinct value: {@link ENV_INVIS_RUN} renders ~435 code points
8
- * as ~4 KB of source and is joined at EVERY interior gap, so a 20-char secret
9
- * compiles a ~75 KB pattern — and `hasEnvBoundSecret` builds one per configured
10
- * var on every PostToolUse output. Env values are stable for the process's
11
- * lifetime, so the cache is bounded by the number of distinct values. Sharing an
12
- * instance is safe because the regex carries no `g`/`y` flag, hence no
13
- * `lastIndex` state to leak between calls.
14
- * @param {string} value
15
- * @returns {RegExp}
16
- */
17
- export function envValueRegex(value: string): RegExp;
18
1
  /**
19
2
  * True when tool output contains the literal value of a configured env-bound
20
3
  * secret. The shape-based secret hint can't match a prefix-less key or a host
21
4
  * credential, so the pre-gate must also fire on the value itself — otherwise
22
5
  * the engine's env-bound redaction never runs. Invisible-tolerant so a
23
6
  * value with spliced Cf chars (which the daemon still redacts) trips it too.
7
+ *
8
+ * Both sides are stripped and compared as substrings, which is exactly
9
+ * equivalent: the value's visible characters appear contiguously in the
10
+ * stripped text iff they appear in the original separated only by invisibles.
11
+ * It is linear and independent of the value's length — a regex with an
12
+ * invisible class at every gap grows ~4 KB per character, and a long secret (a
13
+ * PEM key) overflows V8's regex size limit and throws on every output.
14
+ *
15
+ * Callers pass post-Layer-1 text: terminal escapes are already gone, so a value
16
+ * split by one is contiguous here. Raw text spliced with an escape is not
17
+ * matched.
24
18
  * @param {string} text
25
19
  * @param {NodeJS.ProcessEnv} [env]
26
20
  * @returns {boolean}
@@ -177,7 +177,7 @@ export function emitFailClosed(input: any, message: string, emit?: (fields: Reco
177
177
  * fail-OPEN by default — a warning context and NO `updatedToolOutput`, leaving
178
178
  * the original tool output in the model's view — or the fail-closed
179
179
  * suppression of {@link emitFailClosed} when the caller set
180
- * AGENT_SANITIZER_FAIL_OPEN=0, or opted into Layer 4 (see the policy below).
180
+ * AGENT_SANITIZER_FAIL_OPEN=0.
181
181
  *
182
182
  * This is the hook where the two postures diverge the most, so state the open
183
183
  * one plainly: several of these layers throw on inputs an attacker composes (a
@@ -55,11 +55,10 @@ export function deleteVerbatimSpans(text: string, spans: string[]): {
55
55
  */
56
56
  /**
57
57
  * Run the configured layers over a single text blob. Layer 1 always runs; the
58
- * rest are opt-in via `options`. Layer 4 (`redact`) is the fail-closed path: a
59
- * redactor that throws is rethrown wrapped, so the caller suppresses the
60
- * output rather than emitting an unvetted value. That fail-closed behavior
61
- * also applies to Layer 4's re-scan after a Layer-5 span deletion (see Layer
62
- * 5, below) — a redactor failure there fails the whole call closed too.
58
+ * rest are opt-in via `options`. A Layer 4 (`redact`) redactor that throws is
59
+ * rethrown wrapped, so the caller answers it under its fault posture rather
60
+ * than receiving unvetted text as vetted. The same holds for Layer 4's re-scan
61
+ * after a Layer-5 span deletion (see Layer 5, below).
63
62
  * `reveal` is the pre-Layer-2 text, present only when the HTML splice removed
64
63
  * bytes, so a caller can persist what was hidden for later inspection (see
65
64
  * {@link applyMarkdownPipeline}); the field is omitted otherwise, and also when