Merge branch 'main' into vincentkoc-code/issue-28140-invalid-config-fail-closed

This commit is contained in:
Vincent Koc
2026-03-07 13:47:11 -05:00
committed by GitHub
4 changed files with 25 additions and 3 deletions

View File

@@ -246,6 +246,7 @@ Docs: https://docs.openclaw.ai
- Security/Nostr: harden profile mutation/import loopback guards by failing closed on non-loopback forwarded client headers (`x-forwarded-for` / `x-real-ip`) and rejecting `sec-fetch-site: cross-site`; adds regression coverage for proxy-forwarded and browser cross-site mutation attempts.
- CLI/bootstrap Node version hint maintenance: replace hardcoded nvm `22` instructions in `openclaw.mjs` with `MIN_NODE_MAJOR` interpolation so future minimum-Node bumps keep startup guidance in sync automatically. (#39056) Thanks @onstash.
- Discord/native slash command auth: honor `commands.allowFrom.discord` (and `commands.allowFrom["*"]`) in guild slash-command pre-dispatch authorization so allowlisted senders are no longer incorrectly rejected as unauthorized. (#38794) Thanks @jskoiz and @thewilloftheshadow.
- Outbound/message target normalization: ignore empty legacy `to`/`channelId` fields when explicit `target` is provided so valid target-based sends no longer fail legacy-param validation; includes regression coverage. (#38944) Thanks @Narcooo.
## 2026.3.2

View File

@@ -6,13 +6,17 @@ export const CHANNEL_TARGET_DESCRIPTION =
export const CHANNEL_TARGETS_DESCRIPTION =
"Recipient/channel targets (same format as --target); accepts ids or names when the directory is available.";
function hasNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.trim().length > 0;
}
export function applyTargetToParams(params: {
action: string;
args: Record<string, unknown>;
}): void {
const target = typeof params.args.target === "string" ? params.args.target.trim() : "";
const hasLegacyTo = typeof params.args.to === "string";
const hasLegacyChannelId = typeof params.args.channelId === "string";
const hasLegacyTo = hasNonEmptyString(params.args.to);
const hasLegacyChannelId = hasNonEmptyString(params.args.channelId);
const mode =
MESSAGE_ACTION_TARGET_MODE[params.action as keyof typeof MESSAGE_ACTION_TARGET_MODE] ?? "none";

View File

@@ -17,6 +17,21 @@ describe("normalizeMessageActionInput", () => {
expect("channelId" in normalized).toBe(false);
});
it("ignores empty-string legacy target fields when explicit target is present", () => {
const normalized = normalizeMessageActionInput({
action: "send",
args: {
target: "1214056829",
channelId: "",
to: " ",
},
});
expect(normalized.target).toBe("1214056829");
expect(normalized.to).toBe("1214056829");
expect("channelId" in normalized).toBe(false);
});
it("maps legacy target fields into canonical target", () => {
const normalized = normalizeMessageActionInput({
action: "send",

View File

@@ -19,11 +19,13 @@ export function normalizeMessageActionInput(params: {
const explicitTarget =
typeof normalizedArgs.target === "string" ? normalizedArgs.target.trim() : "";
const hasLegacyTargetFields =
typeof normalizedArgs.to === "string" || typeof normalizedArgs.channelId === "string";
const hasLegacyTarget =
(typeof normalizedArgs.to === "string" && normalizedArgs.to.trim().length > 0) ||
(typeof normalizedArgs.channelId === "string" && normalizedArgs.channelId.trim().length > 0);
if (explicitTarget && hasLegacyTarget) {
if (explicitTarget && hasLegacyTargetFields) {
delete normalizedArgs.to;
delete normalizedArgs.channelId;
}