agent-sanitizer 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/src/rehydrate.mjs +21 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sanitizer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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": {
|
|
@@ -46,6 +46,9 @@
|
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
+
"agent-control-plane-core": "0.2.13",
|
|
50
|
+
"namespace-guard": "0.20.0",
|
|
51
|
+
"sanitizer-engine": "npm:agent-sanitizer@2.1.0",
|
|
49
52
|
"@commitlint/cli": "^21.0.1",
|
|
50
53
|
"@commitlint/config-conventional": "^21.0.1",
|
|
51
54
|
"@eslint/js": "10.0.1",
|
|
@@ -144,8 +147,9 @@
|
|
|
144
147
|
"scripts": {
|
|
145
148
|
"test": "c8 node --test",
|
|
146
149
|
"coverage": "c8 node --test",
|
|
147
|
-
"
|
|
148
|
-
"
|
|
150
|
+
"coverage:hooks": "c8 --config .c8rc.claude-hooks.json node --test plugin/test/*.test.mjs",
|
|
151
|
+
"check": "tsc --noEmit && tsc -p tsconfig.hooks.json --noEmit",
|
|
152
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.hooks.json --noEmit",
|
|
149
153
|
"build:types": "tsc -p tsconfig.build.json",
|
|
150
154
|
"gen:joining-type": "node scripts/gen-joining-type.mjs",
|
|
151
155
|
"lint": "eslint .",
|
package/src/rehydrate.mjs
CHANGED
|
@@ -183,21 +183,31 @@ async function rehydrateEdit(
|
|
|
183
183
|
// (the model supplied the secret's real bytes itself, e.g. a rotation)
|
|
184
184
|
// extracts nothing and is left to the literal resolver below.
|
|
185
185
|
const diskSpans = pairDiskSpans(view, deletions);
|
|
186
|
-
|
|
186
|
+
let intrusion = null;
|
|
187
|
+
for (const matchStart of occurrences(content, oldS)) {
|
|
187
188
|
const matchEnd = matchStart + oldS.length;
|
|
188
|
-
|
|
189
|
-
(
|
|
190
|
-
matchStart <
|
|
191
|
-
|
|
192
|
-
!(matchStart <=
|
|
189
|
+
const secret = diskSpans.find(
|
|
190
|
+
(span) =>
|
|
191
|
+
matchStart < span.end &&
|
|
192
|
+
span.start < matchEnd &&
|
|
193
|
+
!(matchStart <= span.start && span.end <= matchEnd),
|
|
193
194
|
);
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
if (secret) {
|
|
196
|
+
intrusion = { matchStart, matchEnd, secret };
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Report both byte ranges. The refusal is conservative — it fires on an
|
|
201
|
+
// overlap with the redacted span the caller cannot see — so the caller
|
|
202
|
+
// needs the ranges to tell a true overlap from a mis-sized redaction.
|
|
203
|
+
if (intrusion)
|
|
196
204
|
return {
|
|
197
205
|
deny:
|
|
198
|
-
`old_string
|
|
199
|
-
|
|
200
|
-
`
|
|
206
|
+
`old_string does not appear in the sanitized view of ${ti.file_path}; on disk ` +
|
|
207
|
+
`it matches bytes ${intrusion.matchStart}-${intrusion.matchEnd}, which fall ` +
|
|
208
|
+
`inside a ${hint}…] redacted secret at bytes ${intrusion.secret.start}-` +
|
|
209
|
+
`${intrusion.secret.end} that are hidden from your view; edit only text you ` +
|
|
210
|
+
`can see (include each placeholder whole), or ask the user to make this change`,
|
|
201
211
|
};
|
|
202
212
|
const literalRes = rehydrateNewString(
|
|
203
213
|
oldS,
|