fix: revert requireExplicitTarget default to false for backwards compatibility

Keep the security feature available but opt-in rather than opt-out.
Maintainers can decide to change the default in a follow-up PR.
This commit is contained in:
Diogo Ortega 2026-01-26 14:04:33 +00:00
parent e712faadb9
commit e260f38a69
3 changed files with 22 additions and 47 deletions

View File

@ -196,7 +196,7 @@ export type AgentDefaultsConfig = {
/** /**
* When true, require an explicit target for heartbeat delivery (no implicit routing). * When true, require an explicit target for heartbeat delivery (no implicit routing).
* If no explicit target is provided, heartbeat delivery fails with an error. * If no explicit target is provided, heartbeat delivery fails with an error.
* Default: true (strict mode; prevents accidental sends to stale/implicit targets). * Default: false (backwards compatible; falls back to session-derived or "last" routing).
*/ */
requireExplicitTarget?: boolean; requireExplicitTarget?: boolean;
}; };

View File

@ -145,14 +145,8 @@ describe("resolveHeartbeatDeliveryTarget", () => {
}); });
}); });
it("uses last route when requireExplicitTarget is false", () => { it("uses last route by default", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {};
agents: {
defaults: {
heartbeat: { requireExplicitTarget: false },
},
},
};
const entry = { const entry = {
...baseEntry, ...baseEntry,
lastChannel: "whatsapp" as const, lastChannel: "whatsapp" as const,
@ -186,13 +180,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
}); });
it("skips when last route is webchat", () => { it("skips when last route is webchat", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {};
agents: {
defaults: {
heartbeat: { requireExplicitTarget: false },
},
},
};
const entry = { const entry = {
...baseEntry, ...baseEntry,
lastChannel: "webchat" as const, lastChannel: "webchat" as const,
@ -209,9 +197,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
it("applies allowFrom fallback for WhatsApp targets", () => { it("applies allowFrom fallback for WhatsApp targets", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { agents: { defaults: { heartbeat: { target: "whatsapp", to: "+1999" } } },
defaults: { heartbeat: { target: "whatsapp", to: "+1999", requireExplicitTarget: false } },
},
channels: { whatsapp: { allowFrom: ["+1555", "+1666"] } }, channels: { whatsapp: { allowFrom: ["+1555", "+1666"] } },
}; };
const entry = { const entry = {
@ -219,20 +205,18 @@ describe("resolveHeartbeatDeliveryTarget", () => {
lastChannel: "whatsapp" as const, lastChannel: "whatsapp" as const,
lastTo: "+1222", lastTo: "+1222",
}; };
// Note: The reason field is set based on whether the final resolved target expect(resolveHeartbeatDeliveryTarget({ cfg, entry })).toEqual({
// differs from what explicit mode would have returned. With heartbeat mode, channel: "whatsapp",
// the fallback behavior is expected. to: "+1555",
const result = resolveHeartbeatDeliveryTarget({ cfg, entry }); reason: "allowFrom-fallback",
expect(result.channel).toBe("whatsapp"); accountId: undefined,
expect(result.to).toBe("+1555"); lastChannel: "whatsapp",
expect(result.lastChannel).toBe("whatsapp"); lastAccountId: undefined,
expect(result.accountId).toBeUndefined(); });
expect(result.lastAccountId).toBeUndefined();
}); });
it("keeps WhatsApp group targets even with allowFrom set", () => { it("keeps WhatsApp group targets even with allowFrom set", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { defaults: { heartbeat: { requireExplicitTarget: false } } },
channels: { whatsapp: { allowFrom: ["+1555"] } }, channels: { whatsapp: { allowFrom: ["+1555"] } },
}; };
const entry = { const entry = {
@ -251,7 +235,6 @@ describe("resolveHeartbeatDeliveryTarget", () => {
it("normalizes prefixed WhatsApp group targets for heartbeat delivery", () => { it("normalizes prefixed WhatsApp group targets for heartbeat delivery", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { defaults: { heartbeat: { requireExplicitTarget: false } } },
channels: { whatsapp: { allowFrom: ["+1555"] } }, channels: { whatsapp: { allowFrom: ["+1555"] } },
}; };
const entry = { const entry = {
@ -350,7 +333,7 @@ describe("runHeartbeatOnce", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { agents: {
defaults: { defaults: {
heartbeat: { every: "5m", target: "whatsapp", requireExplicitTarget: false }, heartbeat: { every: "5m", target: "whatsapp" },
}, },
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },
@ -407,18 +390,13 @@ describe("runHeartbeatOnce", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { agents: {
defaults: { defaults: {
heartbeat: { every: "30m", prompt: "Default prompt", requireExplicitTarget: false }, heartbeat: { every: "30m", prompt: "Default prompt" },
}, },
list: [ list: [
{ id: "main", default: true }, { id: "main", default: true },
{ {
id: "ops", id: "ops",
heartbeat: { heartbeat: { every: "5m", target: "whatsapp", prompt: "Ops check" },
every: "5m",
target: "whatsapp",
prompt: "Ops check",
requireExplicitTarget: false,
},
}, },
], ],
}, },
@ -486,7 +464,6 @@ describe("runHeartbeatOnce", () => {
heartbeat: { heartbeat: {
every: "5m", every: "5m",
target: "last", target: "last",
requireExplicitTarget: false,
}, },
}, },
}, },
@ -565,7 +542,7 @@ describe("runHeartbeatOnce", () => {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { agents: {
defaults: { defaults: {
heartbeat: { every: "5m", target: "whatsapp", requireExplicitTarget: false }, heartbeat: { every: "5m", target: "whatsapp" },
}, },
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },
@ -624,7 +601,6 @@ describe("runHeartbeatOnce", () => {
every: "5m", every: "5m",
target: "whatsapp", target: "whatsapp",
includeReasoning: true, includeReasoning: true,
requireExplicitTarget: false,
}, },
}, },
}, },
@ -696,7 +672,6 @@ describe("runHeartbeatOnce", () => {
every: "5m", every: "5m",
target: "whatsapp", target: "whatsapp",
includeReasoning: true, includeReasoning: true,
requireExplicitTarget: false,
}, },
}, },
}, },
@ -762,7 +737,7 @@ describe("runHeartbeatOnce", () => {
try { try {
const cfg: ClawdbotConfig = { const cfg: ClawdbotConfig = {
agents: { agents: {
defaults: { heartbeat: { every: "5m", requireExplicitTarget: false } }, defaults: { heartbeat: { every: "5m" } },
list: [{ id: "work", default: true }], list: [{ id: "work", default: true }],
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },
@ -838,7 +813,7 @@ describe("runHeartbeatOnce", () => {
agents: { agents: {
defaults: { defaults: {
workspace: workspaceDir, workspace: workspaceDir,
heartbeat: { every: "5m", target: "whatsapp", requireExplicitTarget: false }, heartbeat: { every: "5m", target: "whatsapp" },
}, },
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },
@ -910,7 +885,7 @@ describe("runHeartbeatOnce", () => {
agents: { agents: {
defaults: { defaults: {
workspace: workspaceDir, workspace: workspaceDir,
heartbeat: { every: "5m", target: "whatsapp", requireExplicitTarget: false }, heartbeat: { every: "5m", target: "whatsapp" },
}, },
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },
@ -974,7 +949,7 @@ describe("runHeartbeatOnce", () => {
agents: { agents: {
defaults: { defaults: {
workspace: workspaceDir, workspace: workspaceDir,
heartbeat: { every: "5m", target: "whatsapp", requireExplicitTarget: false }, heartbeat: { every: "5m", target: "whatsapp" },
}, },
}, },
channels: { whatsapp: { allowFrom: ["*"] } }, channels: { whatsapp: { allowFrom: ["*"] } },

View File

@ -183,7 +183,7 @@ export function resolveHeartbeatDeliveryTarget(params: {
const heartbeat = params.heartbeat ?? cfg.agents?.defaults?.heartbeat; const heartbeat = params.heartbeat ?? cfg.agents?.defaults?.heartbeat;
const rawTarget = heartbeat?.target; const rawTarget = heartbeat?.target;
// FIX-1.4: Default to strict mode - require explicit target for heartbeat delivery // FIX-1.4: Default to strict mode - require explicit target for heartbeat delivery
const requireExplicitTarget = heartbeat?.requireExplicitTarget ?? true; const requireExplicitTarget = heartbeat?.requireExplicitTarget ?? false;
const explicitTo = heartbeat?.to?.trim(); const explicitTo = heartbeat?.to?.trim();
let target: HeartbeatTarget = "last"; let target: HeartbeatTarget = "last";
if (rawTarget === "none" || rawTarget === "last") { if (rawTarget === "none" || rawTarget === "last") {