agent-sanitizer 2.56.1 → 2.57.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.
@@ -713,6 +713,11 @@ export function depLoadHint(
713
713
  * — redactor daemon down, package not loaded) is the sanitizer being UNAVAILABLE,
714
714
  * so it ASKS to keep a human in the loop rather than hard-block on infrastructure.
715
715
  *
716
+ * An UNATTENDED host has no human for that ask to reach, so the ask stops the
717
+ * call and buys no review. Such a host passes `unavailableDecision: DENY` and
718
+ * gets a hard refusal on the clean-parse arm too, with the same reason text.
719
+ * The default stays ASK, so a host that wires nothing keeps today's behavior.
720
+ *
716
721
  * Deliberately knob-blind: this is the fail-CLOSED posture itself, so it ignores
717
722
  * AGENT_SANITIZER_FAIL_OPEN — a host that wires it directly keeps strictness by
718
723
  * construction, with no env var to remember. A host that instead wants the
@@ -720,19 +725,27 @@ export function depLoadHint(
720
725
  * which delegates here when the posture is closed.
721
726
  * @param {boolean} parsedOk whether the input parsed before the failure
722
727
  * @param {unknown} err
723
- * @param {{ messages?: Partial<typeof PRE_TOOL_USE_MESSAGES>, hint?: string }} [opts]
728
+ * @param {{
729
+ * messages?: Partial<typeof PRE_TOOL_USE_MESSAGES>,
730
+ * hint?: string,
731
+ * unavailableDecision?: (typeof PermissionDecision)[keyof typeof PermissionDecision],
732
+ * }} [opts]
724
733
  * @returns {Record<string, unknown>}
725
734
  */
726
735
  export function failClosedFields(parsedOk, err, opts = {}) {
727
- const { hint = depLoadHint(err) } = opts;
736
+ const { hint = depLoadHint(err), unavailableDecision } = opts;
728
737
  // Merged, not substituted — see judgePreToolUseSanitize. This is the call site
729
738
  // where a missing field would throw out of the catch and fail OPEN.
730
739
  const messages = { ...PRE_TOOL_USE_MESSAGES, ...opts.messages };
731
740
  const cause = `${safeErrMessage(err)}${hint}`;
741
+ // An unknown or absent override falls back to ASK rather than being trusted:
742
+ // a typo must not silently widen this arm past the two closed verdicts.
743
+ const unavailable =
744
+ unavailableDecision === PermissionDecision.DENY
745
+ ? PermissionDecision.DENY
746
+ : PermissionDecision.ASK;
732
747
  return {
733
- permissionDecision: parsedOk
734
- ? PermissionDecision.ASK
735
- : PermissionDecision.DENY,
748
+ permissionDecision: parsedOk ? unavailable : PermissionDecision.DENY,
736
749
  permissionDecisionReason: parsedOk
737
750
  ? messages.failed(cause)
738
751
  : messages.unparsable(cause),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sanitizer",
3
- "version": "2.56.1",
3
+ "version": "2.57.0",
4
4
  "description": "Defend an agent against hidden-content injection: strip payload-capable invisible Unicode and ANSI, splice out human-invisible HTML, and flag data-exfil URLs in untrusted text before any model sees it.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -109,6 +109,11 @@ export function depLoadHint(err: unknown, remedy?: string, failedPackages?: () =
109
109
  * — redactor daemon down, package not loaded) is the sanitizer being UNAVAILABLE,
110
110
  * so it ASKS to keep a human in the loop rather than hard-block on infrastructure.
111
111
  *
112
+ * An UNATTENDED host has no human for that ask to reach, so the ask stops the
113
+ * call and buys no review. Such a host passes `unavailableDecision: DENY` and
114
+ * gets a hard refusal on the clean-parse arm too, with the same reason text.
115
+ * The default stays ASK, so a host that wires nothing keeps today's behavior.
116
+ *
112
117
  * Deliberately knob-blind: this is the fail-CLOSED posture itself, so it ignores
113
118
  * AGENT_SANITIZER_FAIL_OPEN — a host that wires it directly keeps strictness by
114
119
  * construction, with no env var to remember. A host that instead wants the
@@ -116,12 +121,17 @@ export function depLoadHint(err: unknown, remedy?: string, failedPackages?: () =
116
121
  * which delegates here when the posture is closed.
117
122
  * @param {boolean} parsedOk whether the input parsed before the failure
118
123
  * @param {unknown} err
119
- * @param {{ messages?: Partial<typeof PRE_TOOL_USE_MESSAGES>, hint?: string }} [opts]
124
+ * @param {{
125
+ * messages?: Partial<typeof PRE_TOOL_USE_MESSAGES>,
126
+ * hint?: string,
127
+ * unavailableDecision?: (typeof PermissionDecision)[keyof typeof PermissionDecision],
128
+ * }} [opts]
120
129
  * @returns {Record<string, unknown>}
121
130
  */
122
131
  export function failClosedFields(parsedOk: boolean, err: unknown, opts?: {
123
132
  messages?: Partial<typeof PRE_TOOL_USE_MESSAGES>;
124
133
  hint?: string;
134
+ unavailableDecision?: (typeof PermissionDecision)[keyof typeof PermissionDecision];
125
135
  }): Record<string, unknown>;
126
136
  /**
127
137
  * True when a faulting PreToolUse call is the one case the OPEN posture must
@@ -225,4 +235,5 @@ export type HostGate = (input: {
225
235
  permission_mode?: string;
226
236
  }) => string | null | undefined;
227
237
  declare const rehydrateRedacted: typeof import("agent-sanitizer/rehydrate").rehydrateRedacted;
238
+ import { PermissionDecision } from "./lib/hook-io.mjs";
228
239
  export {};