mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
Auto-reply: scope allowlist store writes by account (#39015)
* Auto-reply: scope allowlist store writes * Tests: cover allowlist store account scoping * Changelog: note allowlist store scoping hardening
This commit is contained in:
@@ -240,6 +240,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Config/compaction safeguard settings: regression-test `agents.defaults.compaction.recentTurnsPreserve` through `loadConfig()` and cover the new help metadata entry so the exposed preserve knob stays wired through schema validation and config UX. (#25557) thanks @rodrigouroz.
|
||||
- iOS/Quick Setup presentation: skip automatic Quick Setup when a gateway is already configured (active connect config, last-known connection, preferred gateway, or manual host), so reconnecting installs no longer get prompted to connect again. (#38964) Thanks @ngutman.
|
||||
- CLI/Docs memory help accuracy: clarify `openclaw memory status --deep` behavior and align memory command examples/docs with the current search options. (#31803) Thanks @JasonOA888 and @Avi974.
|
||||
- Auto-reply/allowlist store account scoping: keep `/allowlist ... --store` writes scoped to the selected account and clear legacy unscoped entries when removing default-account store access, preventing cross-account default allowlist bleed-through from legacy pairing-store reads. Thanks @vincentkoc.
|
||||
- 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.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
@@ -196,6 +196,31 @@ function extractConfigAllowlist(account: {
|
||||
};
|
||||
}
|
||||
|
||||
async function updatePairingStoreAllowlist(params: {
|
||||
action: "add" | "remove";
|
||||
channelId: ChannelId;
|
||||
accountId?: string;
|
||||
entry: string;
|
||||
}) {
|
||||
const storeEntry = {
|
||||
channel: params.channelId,
|
||||
entry: params.entry,
|
||||
accountId: params.accountId,
|
||||
};
|
||||
if (params.action === "add") {
|
||||
await addChannelAllowFromStoreEntry(storeEntry);
|
||||
return;
|
||||
}
|
||||
|
||||
await removeChannelAllowFromStoreEntry(storeEntry);
|
||||
if (params.accountId === DEFAULT_ACCOUNT_ID) {
|
||||
await removeChannelAllowFromStoreEntry({
|
||||
channel: params.channelId,
|
||||
entry: params.entry,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAccountTarget(
|
||||
parsed: Record<string, unknown>,
|
||||
channelId: ChannelId,
|
||||
@@ -695,11 +720,12 @@ export const handleAllowlistCommand: CommandHandler = async (params, allowTextCo
|
||||
}
|
||||
|
||||
if (shouldTouchStore) {
|
||||
if (parsed.action === "add") {
|
||||
await addChannelAllowFromStoreEntry({ channel: channelId, entry: parsed.entry });
|
||||
} else if (parsed.action === "remove") {
|
||||
await removeChannelAllowFromStoreEntry({ channel: channelId, entry: parsed.entry });
|
||||
}
|
||||
await updatePairingStoreAllowlist({
|
||||
action: parsed.action,
|
||||
channelId,
|
||||
accountId,
|
||||
entry: parsed.entry,
|
||||
});
|
||||
}
|
||||
|
||||
const actionLabel = parsed.action === "add" ? "added" : "removed";
|
||||
@@ -727,11 +753,12 @@ export const handleAllowlistCommand: CommandHandler = async (params, allowTextCo
|
||||
};
|
||||
}
|
||||
|
||||
if (parsed.action === "add") {
|
||||
await addChannelAllowFromStoreEntry({ channel: channelId, entry: parsed.entry });
|
||||
} else if (parsed.action === "remove") {
|
||||
await removeChannelAllowFromStoreEntry({ channel: channelId, entry: parsed.entry });
|
||||
}
|
||||
await updatePairingStoreAllowlist({
|
||||
action: parsed.action,
|
||||
channelId,
|
||||
accountId,
|
||||
entry: parsed.entry,
|
||||
});
|
||||
|
||||
const actionLabel = parsed.action === "add" ? "added" : "removed";
|
||||
const scopeLabel = scope === "dm" ? "DM" : "group";
|
||||
|
||||
@@ -704,10 +704,74 @@ describe("handleCommands /allowlist", () => {
|
||||
expect(addChannelAllowFromStoreEntryMock).toHaveBeenCalledWith({
|
||||
channel: "telegram",
|
||||
entry: "789",
|
||||
accountId: "default",
|
||||
});
|
||||
expect(result.reply?.text).toContain("DM allowlist added");
|
||||
});
|
||||
|
||||
it("writes store entries to the selected account scope", async () => {
|
||||
readConfigFileSnapshotMock.mockResolvedValueOnce({
|
||||
valid: true,
|
||||
parsed: {
|
||||
channels: { telegram: { accounts: { work: { allowFrom: ["123"] } } } },
|
||||
},
|
||||
});
|
||||
validateConfigObjectWithPluginsMock.mockImplementation((config: unknown) => ({
|
||||
ok: true,
|
||||
config,
|
||||
}));
|
||||
addChannelAllowFromStoreEntryMock.mockResolvedValueOnce({
|
||||
changed: true,
|
||||
allowFrom: ["123", "789"],
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
commands: { text: true, config: true },
|
||||
channels: { telegram: { accounts: { work: { allowFrom: ["123"] } } } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildPolicyParams("/allowlist add dm --account work 789", cfg, {
|
||||
AccountId: "work",
|
||||
});
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(addChannelAllowFromStoreEntryMock).toHaveBeenCalledWith({
|
||||
channel: "telegram",
|
||||
entry: "789",
|
||||
accountId: "work",
|
||||
});
|
||||
});
|
||||
|
||||
it("removes default-account entries from scoped and legacy pairing stores", async () => {
|
||||
removeChannelAllowFromStoreEntryMock
|
||||
.mockResolvedValueOnce({
|
||||
changed: true,
|
||||
allowFrom: [],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
changed: true,
|
||||
allowFrom: [],
|
||||
});
|
||||
|
||||
const cfg = {
|
||||
commands: { text: true, config: true },
|
||||
channels: { telegram: { allowFrom: ["123"] } },
|
||||
} as OpenClawConfig;
|
||||
const params = buildPolicyParams("/allowlist remove dm --store 789", cfg);
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(removeChannelAllowFromStoreEntryMock).toHaveBeenNthCalledWith(1, {
|
||||
channel: "telegram",
|
||||
entry: "789",
|
||||
accountId: "default",
|
||||
});
|
||||
expect(removeChannelAllowFromStoreEntryMock).toHaveBeenNthCalledWith(2, {
|
||||
channel: "telegram",
|
||||
entry: "789",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects blocked account ids and keeps Object.prototype clean", async () => {
|
||||
delete (Object.prototype as Record<string, unknown>).allowFrom;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user