fix: harden msteams group access
This commit is contained in:
@@ -65,6 +65,7 @@
|
|||||||
- Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete.
|
- Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete.
|
||||||
- CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete.
|
- CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete.
|
||||||
- Providers: default groupPolicy to allowlist across providers and warn in doctor when groups are open.
|
- Providers: default groupPolicy to allowlist across providers and warn in doctor when groups are open.
|
||||||
|
- MS Teams: add groupPolicy/groupAllowFrom gating for group chats and warn when groups are open.
|
||||||
- Gateway/WebChat: include handshake validation details in the WebSocket close reason for easier debugging; preserve close codes.
|
- Gateway/WebChat: include handshake validation details in the WebSocket close reason for easier debugging; preserve close codes.
|
||||||
- Gateway/Auth: send invalid connect responses before closing the handshake; stabilize invalid-connect auth test.
|
- Gateway/Auth: send invalid connect responses before closing the handshake; stabilize invalid-connect auth test.
|
||||||
- Gateway: tighten gateway listener detection.
|
- Gateway: tighten gateway listener detection.
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/Signal/iMessage)"
|
summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/Signal/iMessage/Microsoft Teams)"
|
||||||
read_when:
|
read_when:
|
||||||
- Changing group chat behavior or mention gating
|
- Changing group chat behavior or mention gating
|
||||||
---
|
---
|
||||||
# Groups
|
# Groups
|
||||||
|
|
||||||
Clawdbot treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage.
|
Clawdbot treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams.
|
||||||
|
|
||||||
## Beginner intro (2 minutes)
|
## Beginner intro (2 minutes)
|
||||||
Clawdbot “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
Clawdbot “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
||||||
@@ -15,7 +15,7 @@ Default behavior:
|
|||||||
- Groups are restricted (`groupPolicy: "allowlist"`).
|
- Groups are restricted (`groupPolicy: "allowlist"`).
|
||||||
- Replies require a mention unless you explicitly disable mention gating.
|
- Replies require a mention unless you explicitly disable mention gating.
|
||||||
|
|
||||||
Translation: anyone in the group can trigger Clawdbot by mentioning it.
|
Translation: allowlisted senders can trigger Clawdbot by mentioning it.
|
||||||
|
|
||||||
> TL;DR
|
> TL;DR
|
||||||
> - **DM access** is controlled by `*.allowFrom`.
|
> - **DM access** is controlled by `*.allowFrom`.
|
||||||
@@ -71,6 +71,10 @@ Control how group/room messages are handled per provider:
|
|||||||
groupPolicy: "disabled",
|
groupPolicy: "disabled",
|
||||||
groupAllowFrom: ["chat_id:123"]
|
groupAllowFrom: ["chat_id:123"]
|
||||||
},
|
},
|
||||||
|
msteams: {
|
||||||
|
groupPolicy: "disabled",
|
||||||
|
groupAllowFrom: ["user@org.com"]
|
||||||
|
},
|
||||||
discord: {
|
discord: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
guilds: {
|
guilds: {
|
||||||
@@ -92,7 +96,7 @@ Control how group/room messages are handled per provider:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||||
- WhatsApp/Telegram/Signal/iMessage: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||||
- Discord: allowlist uses `discord.guilds.<id>.channels`.
|
- Discord: allowlist uses `discord.guilds.<id>.channels`.
|
||||||
- Slack: allowlist uses `slack.channels`.
|
- Slack: allowlist uses `slack.channels`.
|
||||||
- Group DMs are controlled separately (`discord.dm.*`, `slack.dm.*`).
|
- Group DMs are controlled separately (`discord.dm.*`, `slack.dm.*`).
|
||||||
|
|||||||
@@ -529,6 +529,10 @@ Use `*.groupPolicy` to control whether group/room messages are accepted at all:
|
|||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["chat_id:123"]
|
groupAllowFrom: ["chat_id:123"]
|
||||||
},
|
},
|
||||||
|
msteams: {
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
groupAllowFrom: ["user@org.com"]
|
||||||
|
},
|
||||||
discord: {
|
discord: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
guilds: {
|
guilds: {
|
||||||
@@ -548,7 +552,7 @@ Notes:
|
|||||||
- `"open"`: groups bypass allowlists; mention-gating still applies.
|
- `"open"`: groups bypass allowlists; mention-gating still applies.
|
||||||
- `"disabled"`: block all group/room messages.
|
- `"disabled"`: block all group/room messages.
|
||||||
- `"allowlist"`: only allow groups/rooms that match the configured allowlist.
|
- `"allowlist"`: only allow groups/rooms that match the configured allowlist.
|
||||||
- WhatsApp/Telegram/Signal/iMessage use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||||
- Discord/Slack use channel allowlists (`discord.guilds.*.channels`, `slack.channels`).
|
- Discord/Slack use channel allowlists (`discord.guilds.*.channels`, `slack.channels`).
|
||||||
- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.
|
- Group DMs (Discord/Slack) are still controlled by `dm.groupEnabled` + `dm.groupChannels`.
|
||||||
- Default is `groupPolicy: "allowlist"`; if no allowlist is configured, group messages are blocked.
|
- Default is `groupPolicy: "allowlist"`; if no allowlist is configured, group messages are blocked.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Clawdbot has two separate “who can trigger me?” layers:
|
|||||||
- **Group allowlist** (provider-specific): which groups/channels/guilds the bot will accept messages from at all.
|
- **Group allowlist** (provider-specific): which groups/channels/guilds the bot will accept messages from at all.
|
||||||
- Common patterns:
|
- Common patterns:
|
||||||
- `whatsapp.groups`, `telegram.groups`, `imessage.groups`: per-group defaults like `requireMention`; when set, it also acts as a group allowlist (include `"*"` to keep allow-all behavior).
|
- `whatsapp.groups`, `telegram.groups`, `imessage.groups`: per-group defaults like `requireMention`; when set, it also acts as a group allowlist (include `"*"` to keep allow-all behavior).
|
||||||
- `groupPolicy="allowlist"` + `groupAllowFrom`: restrict who can trigger the bot *inside* a group session (WhatsApp/Telegram/Signal/iMessage).
|
- `groupPolicy="allowlist"` + `groupAllowFrom`: restrict who can trigger the bot *inside* a group session (WhatsApp/Telegram/Signal/iMessage/Microsoft Teams).
|
||||||
- `discord.guilds` / `slack.channels`: per-surface allowlists + mention defaults.
|
- `discord.guilds` / `slack.channels`: per-surface allowlists + mention defaults.
|
||||||
|
|
||||||
Details: [Configuration](/gateway/configuration) and [Groups](/concepts/groups)
|
Details: [Configuration](/gateway/configuration) and [Groups](/concepts/groups)
|
||||||
|
|||||||
@@ -30,12 +30,34 @@ Minimal config:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
Note: group chats are blocked by default (`msteams.groupPolicy: "allowlist"`). To allow group replies, set `msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
- Talk to Clawdbot via Teams DMs, group chats, or channels.
|
- Talk to Clawdbot via Teams DMs, group chats, or channels.
|
||||||
- Keep routing deterministic: replies always go back to the provider they arrived on.
|
- Keep routing deterministic: replies always go back to the provider they arrived on.
|
||||||
- Default to safe channel behavior (mentions required unless configured otherwise).
|
- Default to safe channel behavior (mentions required unless configured otherwise).
|
||||||
|
|
||||||
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
|
**DM access**
|
||||||
|
- Default: `msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||||
|
- `msteams.allowFrom` accepts AAD object IDs or UPNs.
|
||||||
|
|
||||||
|
**Group access**
|
||||||
|
- Default: `msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`).
|
||||||
|
- `msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `msteams.allowFrom`).
|
||||||
|
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
msteams: {
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
groupAllowFrom: ["user@org.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
1. Create an **Azure Bot** (App ID + secret + tenant ID).
|
1. Create an **Azure Bot** (App ID + secret + tenant ID).
|
||||||
2. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
2. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
||||||
|
|||||||
@@ -1481,6 +1481,16 @@ describe("legacy config detection", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults msteams.groupPolicy to allowlist when msteams section exists", async () => {
|
||||||
|
vi.resetModules();
|
||||||
|
const { validateConfigObject } = await import("./config.js");
|
||||||
|
const res = validateConfigObject({ msteams: {} });
|
||||||
|
expect(res.ok).toBe(true);
|
||||||
|
if (res.ok) {
|
||||||
|
expect(res.config.msteams?.groupPolicy).toBe("allowlist");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects unsafe executable config values", async () => {
|
it("rejects unsafe executable config values", async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
const { validateConfigObject } = await import("./config.js");
|
const { validateConfigObject } = await import("./config.js");
|
||||||
|
|||||||
@@ -763,6 +763,15 @@ export type MSTeamsConfig = {
|
|||||||
dmPolicy?: DmPolicy;
|
dmPolicy?: DmPolicy;
|
||||||
/** Allowlist for DM senders (AAD object IDs or UPNs). */
|
/** Allowlist for DM senders (AAD object IDs or UPNs). */
|
||||||
allowFrom?: Array<string>;
|
allowFrom?: Array<string>;
|
||||||
|
/** Optional allowlist for group/channel senders (AAD object IDs or UPNs). */
|
||||||
|
groupAllowFrom?: Array<string>;
|
||||||
|
/**
|
||||||
|
* Controls how group/channel messages are handled:
|
||||||
|
* - "open": groups bypass allowFrom; mention-gating applies
|
||||||
|
* - "disabled": block all group messages
|
||||||
|
* - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
||||||
|
*/
|
||||||
|
groupPolicy?: GroupPolicy;
|
||||||
/** Outbound text chunk size (chars). Default: 4000. */
|
/** Outbound text chunk size (chars). Default: 4000. */
|
||||||
textChunkLimit?: number;
|
textChunkLimit?: number;
|
||||||
/** Merge streamed block replies before sending. */
|
/** Merge streamed block replies before sending. */
|
||||||
|
|||||||
@@ -619,6 +619,8 @@ const MSTeamsConfigSchema = z
|
|||||||
.optional(),
|
.optional(),
|
||||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||||
allowFrom: z.array(z.string()).optional(),
|
allowFrom: z.array(z.string()).optional(),
|
||||||
|
groupAllowFrom: z.array(z.string()).optional(),
|
||||||
|
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||||
textChunkLimit: z.number().int().positive().optional(),
|
textChunkLimit: z.number().int().positive().optional(),
|
||||||
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
||||||
mediaAllowHosts: z.array(z.string()).optional(),
|
mediaAllowHosts: z.array(z.string()).optional(),
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
import type { MSTeamsAdapter } from "./messenger.js";
|
import type { MSTeamsAdapter } from "./messenger.js";
|
||||||
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
import type { MSTeamsMonitorLogger } from "./monitor-types.js";
|
||||||
import {
|
import {
|
||||||
|
isMSTeamsGroupAllowed,
|
||||||
resolveMSTeamsReplyPolicy,
|
resolveMSTeamsReplyPolicy,
|
||||||
resolveMSTeamsRouteConfig,
|
resolveMSTeamsRouteConfig,
|
||||||
} from "./policy.js";
|
} from "./policy.js";
|
||||||
@@ -176,6 +177,9 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
|
|
||||||
const senderName = from.name ?? from.id;
|
const senderName = from.name ?? from.id;
|
||||||
const senderId = from.aadObjectId ?? from.id;
|
const senderId = from.aadObjectId ?? from.id;
|
||||||
|
const storedAllowFrom = await readProviderAllowFromStore("msteams").catch(
|
||||||
|
() => [],
|
||||||
|
);
|
||||||
|
|
||||||
// Check DM policy for direct messages
|
// Check DM policy for direct messages
|
||||||
if (isDirectMessage && msteamsCfg) {
|
if (isDirectMessage && msteamsCfg) {
|
||||||
@@ -189,7 +193,6 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
|
|
||||||
if (dmPolicy !== "open") {
|
if (dmPolicy !== "open") {
|
||||||
// Check allowlist - look up from config and pairing store
|
// Check allowlist - look up from config and pairing store
|
||||||
const storedAllowFrom = await readProviderAllowFromStore("msteams");
|
|
||||||
const effectiveAllowFrom = [
|
const effectiveAllowFrom = [
|
||||||
...allowFrom.map((v) => String(v).toLowerCase()),
|
...allowFrom.map((v) => String(v).toLowerCase()),
|
||||||
...storedAllowFrom,
|
...storedAllowFrom,
|
||||||
@@ -225,6 +228,49 @@ function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDirectMessage && msteamsCfg) {
|
||||||
|
const groupPolicy = msteamsCfg.groupPolicy ?? "allowlist";
|
||||||
|
const groupAllowFrom =
|
||||||
|
msteamsCfg.groupAllowFrom ??
|
||||||
|
(msteamsCfg.allowFrom && msteamsCfg.allowFrom.length > 0
|
||||||
|
? msteamsCfg.allowFrom
|
||||||
|
: []);
|
||||||
|
const effectiveGroupAllowFrom = [
|
||||||
|
...groupAllowFrom.map((v) => String(v)),
|
||||||
|
...storedAllowFrom,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (groupPolicy === "disabled") {
|
||||||
|
log.debug("dropping group message (groupPolicy: disabled)", {
|
||||||
|
conversationId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupPolicy === "allowlist") {
|
||||||
|
if (effectiveGroupAllowFrom.length === 0) {
|
||||||
|
log.debug(
|
||||||
|
"dropping group message (groupPolicy: allowlist, no groupAllowFrom)",
|
||||||
|
{ conversationId },
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const allowed = isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy,
|
||||||
|
allowFrom: effectiveGroupAllowFrom,
|
||||||
|
senderId,
|
||||||
|
senderName,
|
||||||
|
});
|
||||||
|
if (!allowed) {
|
||||||
|
log.debug("dropping group message (not in groupAllowFrom)", {
|
||||||
|
sender: senderId,
|
||||||
|
label: senderName,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build conversation reference for proactive replies
|
// Build conversation reference for proactive replies
|
||||||
const agent = activity.recipient;
|
const agent = activity.recipient;
|
||||||
const teamId = activity.channelData?.team?.id;
|
const teamId = activity.channelData?.team?.id;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
|
|
||||||
import type { MSTeamsConfig } from "../config/types.js";
|
import type { MSTeamsConfig } from "../config/types.js";
|
||||||
import {
|
import {
|
||||||
|
isMSTeamsGroupAllowed,
|
||||||
resolveMSTeamsReplyPolicy,
|
resolveMSTeamsReplyPolicy,
|
||||||
resolveMSTeamsRouteConfig,
|
resolveMSTeamsRouteConfig,
|
||||||
} from "./policy.js";
|
} from "./policy.js";
|
||||||
@@ -96,4 +97,72 @@ describe("msteams policy", () => {
|
|||||||
expect(policy).toEqual({ requireMention: false, replyStyle: "thread" });
|
expect(policy).toEqual({ requireMention: false, replyStyle: "thread" });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isMSTeamsGroupAllowed", () => {
|
||||||
|
it("allows when policy is open", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "open",
|
||||||
|
allowFrom: [],
|
||||||
|
senderId: "user-id",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks when policy is disabled", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "disabled",
|
||||||
|
allowFrom: ["user-id"],
|
||||||
|
senderId: "user-id",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks allowlist when empty", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
allowFrom: [],
|
||||||
|
senderId: "user-id",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows allowlist when sender matches", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
allowFrom: ["User-Id"],
|
||||||
|
senderId: "user-id",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows allowlist when sender name matches", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
allowFrom: ["user"],
|
||||||
|
senderId: "other",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows allowlist wildcard", () => {
|
||||||
|
expect(
|
||||||
|
isMSTeamsGroupAllowed({
|
||||||
|
groupPolicy: "allowlist",
|
||||||
|
allowFrom: ["*"],
|
||||||
|
senderId: "other",
|
||||||
|
senderName: "User",
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
GroupPolicy,
|
||||||
MSTeamsChannelConfig,
|
MSTeamsChannelConfig,
|
||||||
MSTeamsConfig,
|
MSTeamsConfig,
|
||||||
MSTeamsReplyStyle,
|
MSTeamsReplyStyle,
|
||||||
@@ -56,3 +57,25 @@ export function resolveMSTeamsReplyPolicy(params: {
|
|||||||
|
|
||||||
return { requireMention, replyStyle };
|
return { requireMention, replyStyle };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMSTeamsGroupAllowed(params: {
|
||||||
|
groupPolicy: GroupPolicy;
|
||||||
|
allowFrom: Array<string | number>;
|
||||||
|
senderId: string;
|
||||||
|
senderName?: string | null;
|
||||||
|
}): boolean {
|
||||||
|
const { groupPolicy } = params;
|
||||||
|
if (groupPolicy === "disabled") return false;
|
||||||
|
if (groupPolicy === "open") return true;
|
||||||
|
const allowFrom = params.allowFrom
|
||||||
|
.map((entry) => String(entry).trim().toLowerCase())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (allowFrom.length === 0) return false;
|
||||||
|
if (allowFrom.includes("*")) return true;
|
||||||
|
const senderId = params.senderId.toLowerCase();
|
||||||
|
const senderName = params.senderName?.toLowerCase();
|
||||||
|
return (
|
||||||
|
allowFrom.includes(senderId) ||
|
||||||
|
(senderName ? allowFrom.includes(senderName) : false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ export const msteamsPlugin: ProviderPlugin<ResolvedMSTeamsAccount> = {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((entry) => entry.toLowerCase()),
|
.map((entry) => entry.toLowerCase()),
|
||||||
},
|
},
|
||||||
|
security: {
|
||||||
|
collectWarnings: ({ cfg }) => {
|
||||||
|
const groupPolicy = cfg.msteams?.groupPolicy ?? "allowlist";
|
||||||
|
if (groupPolicy !== "open") return [];
|
||||||
|
return [
|
||||||
|
`- MS Teams groups: groupPolicy="open" allows any member to trigger (mention-gated). Set msteams.groupPolicy="allowlist" + msteams.groupAllowFrom to restrict senders.`,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
setup: {
|
setup: {
|
||||||
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
||||||
applyAccountConfig: ({ cfg }) => ({
|
applyAccountConfig: ({ cfg }) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user