mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 23:32:12 +03:00
fix(claude): preserve signed thinking turns during obfuscation (#8629)
Validated in local merge-train T6 (ungrouped batch 1)
This commit is contained in:
@@ -87,9 +87,18 @@ export function obfuscateInBody(body: Record<string, unknown>): void {
|
||||
if (typeof content === "string") {
|
||||
msg.content = obfuscateSensitiveWords(content);
|
||||
} else if (Array.isArray(content)) {
|
||||
for (const block of content as Array<Record<string, unknown>>) {
|
||||
if (typeof block.text === "string") {
|
||||
block.text = obfuscateSensitiveWords(block.text);
|
||||
// Anthropic verifies a signature over a thinking turn. Mutating a text
|
||||
// sibling in that same turn invalidates it and makes the next request
|
||||
// fail with `Invalid signature in thinking block`.
|
||||
const blocks = content as Array<Record<string, unknown>>;
|
||||
const hasSignedThinking = blocks.some(
|
||||
(block) => block?.type === "thinking" || block?.type === "redacted_thinking"
|
||||
);
|
||||
if (!hasSignedThinking) {
|
||||
for (const block of blocks) {
|
||||
if (typeof block.text === "string") {
|
||||
block.text = obfuscateSensitiveWords(block.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,9 +341,17 @@ function applyObfuscateWords(body: RequestBody, op: ObfuscateWordsOp): void {
|
||||
if (typeof content === "string") {
|
||||
msg.content = obfuscateWithList(content, words);
|
||||
} else if (Array.isArray(content)) {
|
||||
for (const block of content as Array<Record<string, unknown>>) {
|
||||
if (typeof block.text === "string") {
|
||||
block.text = obfuscateWithList(block.text, words);
|
||||
// A signed Anthropic thinking turn covers its text siblings too. Leave
|
||||
// the entire turn byte-for-byte intact so its signature remains valid.
|
||||
const blocks = content as Array<Record<string, unknown>>;
|
||||
const hasSignedThinking = blocks.some(
|
||||
(block) => block?.type === "thinking" || block?.type === "redacted_thinking"
|
||||
);
|
||||
if (!hasSignedThinking) {
|
||||
for (const block of blocks) {
|
||||
if (typeof block.text === "string") {
|
||||
block.text = obfuscateWithList(block.text, words);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
tests/unit/claude-code-obfuscation.test.ts
Normal file
30
tests/unit/claude-code-obfuscation.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { obfuscateInBody } = await import("../../open-sse/services/claudeCodeObfuscation.ts");
|
||||
|
||||
test("obfuscateInBody preserves a turn that carries signed thinking", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "private", signature: "signed-by-anthropic" },
|
||||
{ type: "text", text: "I used opencode to inspect it." },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
obfuscateInBody(body);
|
||||
assert.equal((body.messages[0].content[1] as { text: string }).text, "I used opencode to inspect it.");
|
||||
});
|
||||
|
||||
test("obfuscateInBody still obfuscates an unsigned normal text turn", () => {
|
||||
const body = {
|
||||
messages: [{ role: "assistant", content: [{ type: "text", text: "I used opencode." }] }],
|
||||
};
|
||||
|
||||
obfuscateInBody(body);
|
||||
assert.notEqual((body.messages[0].content[0] as { text: string }).text, "I used opencode.");
|
||||
});
|
||||
@@ -145,6 +145,25 @@ test("obfuscate_words with empty list is a no-op", () => {
|
||||
assert.equal((body.system[0] as { text: string }).text, "opencode");
|
||||
});
|
||||
|
||||
test("obfuscate_words preserves a message turn with signed thinking", () => {
|
||||
const body = {
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "thinking", thinking: "private", signature: "signed-by-anthropic" },
|
||||
{ type: "text", text: "opencode remains unchanged" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
applyTransformPipeline(body, [{ kind: "obfuscate_words", words: ["opencode"] }]);
|
||||
assert.equal(
|
||||
(body.messages[0].content[1] as { text: string }).text,
|
||||
"opencode remains unchanged"
|
||||
);
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Pipeline ordering: drop paragraph then obfuscate what survives
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user