Skip to content

Commit 7cc44c2

Browse files
authored
chore: move exec approval note to release process
1 parent 8cc75e2 commit 7cc44c2

11 files changed

Lines changed: 331 additions & 82 deletions

docs/.i18n/glossary.zh-CN.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,18 @@
12271227
"source": "cohere",
12281228
"target": "cohere"
12291229
},
1230+
{
1231+
"source": "Meta",
1232+
"target": "Meta"
1233+
},
1234+
{
1235+
"source": "Meta plugin",
1236+
"target": "Meta 插件"
1237+
},
1238+
{
1239+
"source": "meta",
1240+
"target": "meta"
1241+
},
12301242
{
12311243
"source": "Zalo ClawBot",
12321244
"target": "Zalo ClawBot"

src/agents/transcript-redact.test.ts

Lines changed: 192 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ function cfg(mode: "tools" | "off", patterns?: string[]): OpenClawConfig {
2828
} satisfies OpenClawConfig;
2929
}
3030

31+
function googleCompatCfg(): OpenClawConfig {
32+
return {
33+
...cfg("tools"),
34+
models: {
35+
providers: {
36+
"google-compatible-proxy": {
37+
api: "openai-completions",
38+
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
39+
models: [],
40+
},
41+
},
42+
},
43+
} satisfies OpenClawConfig;
44+
}
45+
3146
const EMAIL_PATTERN = String.raw`([\w]|[-.])+@([\w]|[-.])+\.\w+`;
3247
const IMAGE_BASE64_WITH_SECRET_TOKEN_SUBSTRING =
3348
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAARcnVOZAAAAKIDABCDEFGHIJKLMNOP8JJRuAAAAABJRU5ErkJggg==";
@@ -39,6 +54,10 @@ const CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES =
3954
"gAAAAABpQnQrXzzZqcAfo3unbAY-ku84xgsvB0fpLkbDvSh3WS5qzfSCmcgwr8_abcdefghijvK2RyV2GQ4ohzcfYwhRwTvY76TvR7Tvr_";
4055
const GOOGLE_THOUGHT_SIGNATURE = Buffer.from(`thought-${"x".repeat(32)}`).toString("base64");
4156
const SHORT_GOOGLE_THOUGHT_SIGNATURE = "c2ln";
57+
const GOOGLE_CREDENTIAL_COLLISION = `AAAAAIza${"A".repeat(20)}`;
58+
const ALIBABA_CREDENTIAL_COLLISION = `AAAALTAI${"B".repeat(12)}`;
59+
const OPAQUE_CREDENTIAL_COLLISION = `signature.v2:${GOOGLE_CREDENTIAL_COLLISION}`;
60+
const OPENAI_COMPAT_OPAQUE_COLLISION = `SIG-${GOOGLE_CREDENTIAL_COLLISION}`;
4261
const COPILOT_CONNECTION_BOUND_ID = Buffer.from(`message-${"y".repeat(24)}`).toString("base64");
4362
const OPENAI_REASONING_REPLAY_METADATA = {
4463
v: 1,
@@ -317,7 +336,7 @@ describe("redactTranscriptMessage", () => {
317336
type: "toolCall",
318337
id: "call_1",
319338
name: "send_request",
320-
thoughtSignature: CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES,
339+
thoughtSignature: GOOGLE_THOUGHT_SIGNATURE,
321340
arguments: {
322341
apiKey: "plainsecretvalue123",
323342
thinkingSignature: "sk-abcdef1234567890xyz",
@@ -345,7 +364,7 @@ describe("redactTranscriptMessage", () => {
345364
arguments: Record<string, string>;
346365
}>
347366
)[0];
348-
expect(block.thoughtSignature).toBe(CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES);
367+
expect(block.thoughtSignature).toBe(GOOGLE_THOUGHT_SIGNATURE);
349368
expect(JSON.stringify(block.arguments)).not.toContain("sk-abcdef1234567890xyz");
350369
expect(block.arguments.apiKey).toBe("plains…e123");
351370
});
@@ -369,7 +388,7 @@ describe("redactTranscriptMessage", () => {
369388
{
370389
type: "thinking",
371390
thinking: "secret sk-abcdef1234567890xyz",
372-
thought_signature: CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES,
391+
thought_signature: SHORT_GOOGLE_THOUGHT_SIGNATURE,
373392
},
374393
],
375394
} as unknown as AgentMessage;
@@ -380,7 +399,7 @@ describe("redactTranscriptMessage", () => {
380399
expect(blocks[0].textSignature).toBe(GOOGLE_THOUGHT_SIGNATURE);
381400
expect(blocks[1].textSignature).not.toContain("sk-abcdef1234567890xyz");
382401
expect(blocks[2].thinking).not.toContain("sk-abcdef1234567890xyz");
383-
expect(blocks[2].thought_signature).toBe(CIPHERTEXT_WITH_TOKEN_SHAPED_BYTES);
402+
expect(blocks[2].thought_signature).toBe(SHORT_GOOGLE_THOUGHT_SIGNATURE);
384403
});
385404

386405
it.each(["openai-responses", "openclaw-openai-responses-transport"])(
@@ -444,14 +463,13 @@ describe("redactTranscriptMessage", () => {
444463
expect(redactedBlock.metadata.accessToken).toBe("nested…t123");
445464
});
446465

447-
it("redacts credential-shaped values even on provider signature fields", () => {
448-
const googleApiKey = `AIza${"a".repeat(32)}`;
466+
it("preserves credential-shaped bytes in recognized provider replay fields", () => {
449467
const githubToken = `ghp_${"b".repeat(36)}`;
450-
const awsAccessKey = "AKIAIOSFODNN7EXAMPLE";
451468
const encryptedDetail = JSON.stringify({
452469
type: "reasoning.encrypted",
453470
data: githubToken,
454471
id: "reasoning-encrypted-1",
472+
secret: "sk-abcdef1234567890xyz",
455473
});
456474
const googleMsg = {
457475
role: "assistant",
@@ -463,22 +481,27 @@ describe("redactTranscriptMessage", () => {
463481
id: "call_1",
464482
name: "send_request",
465483
arguments: {},
466-
thoughtSignature: awsAccessKey,
484+
thoughtSignature: GOOGLE_CREDENTIAL_COLLISION,
467485
},
468486
{
469-
type: "toolCall",
470-
id: "call_2",
471-
name: "send_request",
472-
arguments: {},
473-
thoughtSignature: googleApiKey,
487+
type: "thinking",
488+
thinking: "visible",
489+
thinkingSignature: ALIBABA_CREDENTIAL_COLLISION,
474490
},
475491
],
476492
} as unknown as AgentMessage;
477493
const anthropicMsg = {
478494
role: "assistant",
479-
api: "anthropic-messages",
480-
provider: "anthropic",
481-
content: [{ type: "redacted_thinking", data: githubToken }],
495+
api: "bedrock-converse-stream",
496+
provider: "amazon-bedrock",
497+
content: [
498+
{
499+
type: "thinking",
500+
thinking: "visible",
501+
signature: OPENAI_COMPAT_OPAQUE_COLLISION,
502+
},
503+
{ type: "redacted_thinking", data: githubToken },
504+
],
482505
} as unknown as AgentMessage;
483506
const openAICompletionsMsg = {
484507
role: "assistant",
@@ -492,8 +515,93 @@ describe("redactTranscriptMessage", () => {
492515
arguments: {},
493516
thoughtSignature: encryptedDetail,
494517
},
518+
{
519+
type: "toolCall",
520+
id: "call_secret",
521+
name: "send_request",
522+
arguments: {},
523+
thoughtSignature: githubToken,
524+
},
495525
],
496526
} as unknown as AgentMessage;
527+
const googleOpenAICompletionsMsg = {
528+
role: "assistant",
529+
api: "openclaw-openai-completions-transport",
530+
model: "gemini-3.1-pro",
531+
provider: "google-compatible-proxy",
532+
content: [
533+
{
534+
type: "toolCall",
535+
id: "call_2",
536+
name: "send_request",
537+
arguments: {},
538+
thoughtSignature: OPENAI_COMPAT_OPAQUE_COLLISION,
539+
},
540+
],
541+
} as unknown as AgentMessage;
542+
const openAIResponsesMsg = {
543+
role: "assistant",
544+
api: "openai-responses",
545+
model: "gpt-5.5",
546+
provider: "openai",
547+
content: [
548+
{
549+
type: "thinking",
550+
thinking: "visible",
551+
thinkingSignature: JSON.stringify({
552+
id: "reasoning-1",
553+
type: "reasoning",
554+
encrypted_content: GOOGLE_CREDENTIAL_COLLISION,
555+
summary: [{ type: "summary_text", text: "secret sk-abcdef1234567890xyz" }],
556+
secret: "sk-abcdef1234567890xyz",
557+
}),
558+
},
559+
],
560+
} as unknown as AgentMessage;
561+
562+
const googleBlocks = msgContent(redactTranscriptMessage(googleMsg, cfg("tools"))) as Array<
563+
Record<string, string>
564+
>;
565+
expect(googleBlocks[0].thoughtSignature).toBe(GOOGLE_CREDENTIAL_COLLISION);
566+
expect(googleBlocks[1].thinkingSignature).toBe(ALIBABA_CREDENTIAL_COLLISION);
567+
568+
const anthropicBlocks = msgContent(
569+
redactTranscriptMessage(anthropicMsg, cfg("tools")),
570+
) as Array<Record<string, string>>;
571+
expect(anthropicBlocks[0].signature).toBe(OPENAI_COMPAT_OPAQUE_COLLISION);
572+
expect(anthropicBlocks[1].data).toBe(githubToken);
573+
574+
const completionsBlocks = msgContent(
575+
redactTranscriptMessage(openAICompletionsMsg, cfg("tools")),
576+
) as Array<{ thoughtSignature: string }>;
577+
expect(JSON.parse(completionsBlocks[0].thoughtSignature)).toEqual({
578+
type: "reasoning.encrypted",
579+
data: githubToken,
580+
id: "reasoning-encrypted-1",
581+
});
582+
expect(completionsBlocks[1].thoughtSignature).not.toBe(githubToken);
583+
584+
const googleCompletionsBlock = (
585+
msgContent(redactTranscriptMessage(googleOpenAICompletionsMsg, googleCompatCfg())) as Array<{
586+
thoughtSignature: string;
587+
}>
588+
)[0];
589+
expect(googleCompletionsBlock.thoughtSignature).toBe(OPENAI_COMPAT_OPAQUE_COLLISION);
590+
591+
const responsesBlock = (
592+
msgContent(redactTranscriptMessage(openAIResponsesMsg, cfg("tools"))) as Array<{
593+
thinkingSignature: string;
594+
}>
595+
)[0];
596+
expect(JSON.parse(responsesBlock.thinkingSignature)).toEqual({
597+
id: "reasoning-1",
598+
type: "reasoning",
599+
summary: [],
600+
encrypted_content: GOOGLE_CREDENTIAL_COLLISION,
601+
});
602+
});
603+
604+
it("keeps unknown and malformed replay fields credential-safe", () => {
497605
const customProviderMsg = {
498606
role: "assistant",
499607
api: "custom-provider-api",
@@ -503,26 +611,81 @@ describe("redactTranscriptMessage", () => {
503611
{
504612
type: "thinking",
505613
thinking: "visible",
506-
thinkingSignature: awsAccessKey,
614+
thinkingSignature: ALIBABA_CREDENTIAL_COLLISION,
615+
},
616+
{
617+
type: "toolCall",
618+
id: "call_1",
619+
name: "send_request",
620+
arguments: {},
621+
thoughtSignature: JSON.stringify({
622+
type: "reasoning.encrypted",
623+
data: GOOGLE_CREDENTIAL_COLLISION,
624+
id: "reasoning-encrypted-1",
625+
}),
626+
},
627+
],
628+
} as unknown as AgentMessage;
629+
const malformedGoogleMsg = {
630+
role: "assistant",
631+
api: "google-generative-ai",
632+
model: "gemini-3.1-pro",
633+
provider: "google",
634+
content: [
635+
{
636+
type: "toolCall",
637+
id: "call_1",
638+
name: "send_request",
639+
arguments: {},
640+
thoughtSignature: OPAQUE_CREDENTIAL_COLLISION,
507641
},
508642
],
509643
} as unknown as AgentMessage;
644+
const malformedKnownOpaqueMessages = [
645+
{
646+
role: "assistant",
647+
api: "anthropic-messages",
648+
model: "claude-opus-4-8",
649+
provider: "anthropic",
650+
content: [
651+
{
652+
type: "thinking",
653+
thinking: "visible",
654+
thinkingSignature: "secret sk-abcdef1234567890xyz",
655+
},
656+
],
657+
},
658+
{
659+
role: "assistant",
660+
api: "openai-completions",
661+
model: "gemini-3.1-pro",
662+
provider: "google",
663+
content: [
664+
{
665+
type: "toolCall",
666+
id: "call_1",
667+
name: "send_request",
668+
arguments: {},
669+
thoughtSignature: "secret sk-abcdef1234567890xyz",
670+
},
671+
],
672+
},
673+
] as unknown as AgentMessage[];
510674

511675
expect(
512-
JSON.stringify(msgContent(redactTranscriptMessage(googleMsg, cfg("tools")))),
513-
).not.toContain(awsAccessKey);
514-
expect(
515-
JSON.stringify(msgContent(redactTranscriptMessage(googleMsg, cfg("tools")))),
516-
).not.toContain(googleApiKey);
517-
expect(
518-
JSON.stringify(msgContent(redactTranscriptMessage(anthropicMsg, cfg("tools")))),
519-
).not.toContain(githubToken);
520-
expect(
521-
JSON.stringify(msgContent(redactTranscriptMessage(openAICompletionsMsg, cfg("tools")))),
522-
).not.toContain(githubToken);
676+
JSON.stringify(msgContent(redactTranscriptMessage(customProviderMsg, cfg("tools")))),
677+
).not.toContain(ALIBABA_CREDENTIAL_COLLISION);
523678
expect(
524679
JSON.stringify(msgContent(redactTranscriptMessage(customProviderMsg, cfg("tools")))),
525-
).not.toContain(awsAccessKey);
680+
).not.toContain(GOOGLE_CREDENTIAL_COLLISION);
681+
expect(
682+
JSON.stringify(msgContent(redactTranscriptMessage(malformedGoogleMsg, cfg("tools")))),
683+
).not.toContain(OPAQUE_CREDENTIAL_COLLISION);
684+
for (const message of malformedKnownOpaqueMessages) {
685+
expect(
686+
JSON.stringify(msgContent(redactTranscriptMessage(message, cfg("tools")))),
687+
).not.toContain("sk-abcdef1234567890xyz");
688+
}
526689
});
527690

528691
it("redacts provider-shaped fields when the assistant route is missing", () => {

0 commit comments

Comments
 (0)