Skip to content

Commit 31703de

Browse files
authored
fix: sanitize oversized plugin command arguments (#104015)
1 parent ee7c2c1 commit 31703de

2 files changed

Lines changed: 25 additions & 6 deletions

File tree

src/plugins/commands.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,29 @@ describe("registerPluginCommand", () => {
739739
expect(observedOwnerStatus).toBeUndefined();
740740
});
741741

742+
it("sanitizes oversized arguments before passing them to plugin handlers", async () => {
743+
let observedArgs: string | undefined;
744+
registerVoiceCommandForTest({
745+
acceptsArgs: true,
746+
handler: async (ctx) => {
747+
observedArgs = ctx.args;
748+
return { text: "ok" };
749+
},
750+
});
751+
const match = requirePluginCommandMatch(`/voice \0${"a".repeat(4094)}😀tail`);
752+
753+
await executePluginCommand({
754+
command: match.command,
755+
args: match.args,
756+
channel: "telegram",
757+
isAuthorizedSender: true,
758+
commandBody: "/voice",
759+
config: {},
760+
});
761+
762+
expect(observedArgs).toBe("a".repeat(4094));
763+
});
764+
742765
it("ignores owner status opt-in from direct plugin command registration", async () => {
743766
let observedOwnerStatus: boolean | undefined;
744767
registerPluginCommand("demo-plugin", {

src/plugins/commands.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
9+
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
910
import { resolveBoundAgentIdForSession } from "../agents/session-agent-binding.js";
1011
import { resolveConversationBindingContext } from "../channels/conversation-binding-context.js";
1112
import type { OpenClawConfig } from "../config/types.openclaw.js";
@@ -122,14 +123,9 @@ function sanitizeArgs(args: string | undefined): string | undefined {
122123
return undefined;
123124
}
124125

125-
// Enforce length limit
126-
if (args.length > MAX_ARGS_LENGTH) {
127-
return args.slice(0, MAX_ARGS_LENGTH);
128-
}
129-
130126
// Remove control characters (except newlines and tabs which may be intentional)
131127
let sanitized = "";
132-
for (const char of args) {
128+
for (const char of truncateUtf16Safe(args, MAX_ARGS_LENGTH)) {
133129
const code = char.charCodeAt(0);
134130
const isControl = (code <= 0x1f && code !== 0x09 && code !== 0x0a) || code === 0x7f;
135131
if (!isControl) {

0 commit comments

Comments
 (0)