fix(file-transfer): validate inline write base64 · openclaw/openclaw@23cfc81
vincentkoc
·
2026-05-14
·
via Recent Commits to openclaw:main
File tree
extensions/file-transfer/src/tools
| Original file line number | Diff line number | Diff line change |
|---|
@@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
|
36 | 36 | - Gateway HTTP: match models, session kill, and session history route paths without trusting malformed Host headers, avoiding pre-auth 500s on those endpoints. |
37 | 37 | - Google Meet/Codex: report malformed node proxy `payloadJSON` responses with plugin-owned errors instead of leaking raw JSON parser failures. |
38 | 38 | - Debug proxy: reject malformed relative-form proxy targets with a controlled 400 response instead of letting URL parsing escape the request handler. |
| 39 | +- File transfer: reject malformed inline `file_write` base64 before computing hashes or invoking paired nodes, avoiding Node's lenient base64 decoder. |
39 | 40 | - Models config/auth: stop inferring provider env-var markers from broad `^[A-Z_][A-Z0-9_]*$` strings, and resolve config-backed provider `apiKey` values only through structured env SecretRefs (`secrets.providers[id]` / `secrets.defaults`), so unrelated env vars cannot accidentally become provider credentials. Thanks @sallyom. |
40 | 41 | - Media fetch: skip allocating and buffering the response body for bodyless media responses (HEAD probes and 204-style empty bodies), avoiding wasted heap on streams that carry no payload. Thanks @shakkernerd. |
41 | 42 | - CLI/onboarding: forward provider-specific auth flags (e.g. `--openai-api-key`) through the onboarding wizard so they reach provider auth methods via `ctx.opts`, letting `--openai-api-key "$OPENAI_API_KEY"` skip the redundant "use existing env var?" prompt in non-interactive harnesses. (#81669) Thanks @sjf. |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime"; |
| 2 | +import { describe, expect, it, vi } from "vitest"; |
| 3 | +import { createFileWriteTool } from "./file-write-tool.js"; |
| 4 | + |
| 5 | +vi.mock("openclaw/plugin-sdk/agent-harness-runtime", () => ({ |
| 6 | +callGatewayTool: vi.fn(), |
| 7 | +listNodes: vi.fn(), |
| 8 | +resolveNodeIdFromList: vi.fn(), |
| 9 | +})); |
| 10 | + |
| 11 | +vi.mock("openclaw/plugin-sdk/media-store", () => ({ |
| 12 | +readMediaBuffer: vi.fn(), |
| 13 | +})); |
| 14 | + |
| 15 | +describe("file_write tool", () => { |
| 16 | +it("rejects malformed inline base64 before invoking the node", async () => { |
| 17 | +const tool = createFileWriteTool(); |
| 18 | + |
| 19 | +await expect( |
| 20 | +tool.execute("tool-call-1", { |
| 21 | +node: "node-1", |
| 22 | +path: "/tmp/out.txt", |
| 23 | +contentBase64: "AAA@@@", |
| 24 | +}), |
| 25 | +).rejects.toThrow("contentBase64 is not valid base64"); |
| 26 | + |
| 27 | +expect(callGatewayTool).not.toHaveBeenCalled(); |
| 28 | +}); |
| 29 | +}); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -21,6 +21,18 @@ import {
|
21 | 21 | FILE_WRITE_TOOL_DESCRIPTOR, |
22 | 22 | } from "./descriptors.js"; |
23 | 23 | |
| 24 | +function normalizeBase64ForCompare(value: string): string { |
| 25 | +return value.replace(/=+$/u, "").replace(/-/gu, "+").replace(/_/gu, "/"); |
| 26 | +} |
| 27 | + |
| 28 | +function decodeStrictBase64(value: string): Buffer { |
| 29 | +const buffer = Buffer.from(value, "base64"); |
| 30 | +if (normalizeBase64ForCompare(buffer.toString("base64")) !== normalizeBase64ForCompare(value)) { |
| 31 | +throw new Error("contentBase64 is not valid base64"); |
| 32 | +} |
| 33 | +return buffer; |
| 34 | +} |
| 35 | + |
24 | 36 | async function readSourceBytes(input: { |
25 | 37 | contentBase64?: string; |
26 | 38 | sourceMediaId?: string; |
@@ -37,7 +49,7 @@ async function readSourceBytes(input: {
|
37 | 49 | if (input.contentBase64 === undefined) { |
38 | 50 | throw new Error("contentBase64 or sourceMediaId required"); |
39 | 51 | } |
40 | | -const buffer = Buffer.from(input.contentBase64, "base64"); |
| 52 | +const buffer = decodeStrictBase64(input.contentBase64); |
41 | 53 | return { buffer, contentBase64: input.contentBase64, source: "inline" }; |
42 | 54 | } |
43 | 55 | |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。