Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions charts/openab/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ metadata:
data:
config.toml: |
{{- if ($cfg.discord).enabled }}
{{- if not $cfg.discord.allowedChannels }}
{{- fail (printf "agents.%s.discord.allowedChannels is required — empty means the bot will not respond to any messages. Add at least one channel ID." $name) }}
{{- end }}
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allow_all_channels = {{ if and (hasKey $cfg.discord "allowAllChannels") (ne $cfg.discord.allowAllChannels nil) }}{{ $cfg.discord.allowAllChannels }}{{ else if $cfg.discord.allowedChannels }}false{{ else }}true{{ end }}
allow_all_users = {{ if and (hasKey $cfg.discord "allowAllUsers") (ne $cfg.discord.allowAllUsers nil) }}{{ $cfg.discord.allowAllUsers }}{{ else if $cfg.discord.allowedUsers }}false{{ else }}true{{ end }}
{{- range $cfg.discord.allowedChannels }}
{{- if regexMatch "e\\+|E\\+" (toString .) }}
{{- fail (printf "discord.allowedChannels contains a mangled ID: %s — use --set-string instead of --set for channel IDs" (toString .)) }}
Expand Down Expand Up @@ -55,12 +54,11 @@ data:
{{- end }}

{{- if and ($cfg.slack).enabled }}
{{- if not (($cfg.slack).allowedChannels) }}
{{- fail (printf "agents.%s.slack.allowedChannels is required — empty means the bot will not respond to any messages. Add at least one channel ID." $name) }}
{{- end }}
[slack]
bot_token = "${SLACK_BOT_TOKEN}"
app_token = "${SLACK_APP_TOKEN}"
allow_all_channels = {{ if and (hasKey ($cfg.slack) "allowAllChannels") (ne ($cfg.slack).allowAllChannels nil) }}{{ ($cfg.slack).allowAllChannels }}{{ else if ($cfg.slack).allowedChannels }}false{{ else }}true{{ end }}
allow_all_users = {{ if and (hasKey ($cfg.slack) "allowAllUsers") (ne ($cfg.slack).allowAllUsers nil) }}{{ ($cfg.slack).allowAllUsers }}{{ else if ($cfg.slack).allowedUsers }}false{{ else }}true{{ end }}
{{- range ($cfg.slack).allowedChannels }}
{{- if regexMatch "e\\+|E\\+" (toString .) }}
{{- fail (printf "slack.allowedChannels contains a mangled ID: %s — use --set-string instead of --set for channel IDs" (toString .)) }}
Expand Down
6 changes: 0 additions & 6 deletions charts/openab/tests/adapter-enablement_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ tests:
- it: renders [slack] when enabled=true
set:
agents.kiro.slack.enabled: true
agents.kiro.slack.allowedChannels:
- "C0123456789"
asserts:
- matchRegex:
path: data["config.toml"]
Expand All @@ -51,8 +49,6 @@ tests:
- it: renders [slack] with placeholder tokens when enabled=true
set:
agents.kiro.slack.enabled: true
agents.kiro.slack.allowedChannels:
- "C0123456789"
asserts:
- matchRegex:
path: data["config.toml"]
Expand All @@ -65,8 +61,6 @@ tests:
set:
agents.kiro.discord.enabled: true
agents.kiro.slack.enabled: true
agents.kiro.slack.allowedChannels:
- "C0123456789"
asserts:
- matchRegex:
path: data["config.toml"]
Expand Down
19 changes: 0 additions & 19 deletions charts/openab/tests/configmap_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,27 +92,8 @@ tests:
- it: renders slack allow_user_messages = "multibot-mentions"
set:
agents.kiro.slack.enabled: true
agents.kiro.slack.allowedChannels:
- "C0123456789"
agents.kiro.slack.allowUserMessages: multibot-mentions
asserts:
- matchRegex:
path: data["config.toml"]
pattern: 'allow_user_messages = "multibot-mentions"'

- it: rejects empty discord allowedChannels
set:
agents.kiro.discord.enabled: true
agents.kiro.discord.allowedChannels: []
asserts:
- failedTemplate:
errorPattern: "discord.allowedChannels is required"

- it: rejects empty slack allowedChannels
set:
agents.kiro.discord.enabled: false
agents.kiro.slack.enabled: true
agents.kiro.slack.allowedChannels: []
asserts:
- failedTemplate:
errorPattern: "slack.allowedChannels is required"
11 changes: 8 additions & 3 deletions charts/openab/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,15 @@ agents:
enabled: true # set to false to disable the discord adapter
# botToken is no longer required at render time; the actual token
# is injected at runtime via the DISCORD_BOT_TOKEN env var.
# allowAllChannels: true (default) = allow all channels | false = only allowedChannels
# If not set, auto-inferred: non-empty allowedChannels → false, empty → true
# allowAllUsers: true (default) = allow all users | false = only allowedUsers
# If not set, auto-inferred: non-empty allowedUsers → false, empty → true
# ⚠️ Use --set-string for channel IDs to avoid float64 precision loss
allowedChannels:
- "YOUR_CHANNEL_ID"
# ⚠️ Use --set-string for user IDs to avoid float64 precision loss
allowedUsers: [] # empty = allow all users (default)
allowedUsers: [] # only checked when allowAllUsers=false
# allowBotMessages: "off" (default) | "mentions" | "all"
# recommended for multi-agent collaboration
allowBotMessages: "off"
Expand All @@ -135,8 +139,9 @@ agents:
enabled: false
botToken: "" # Bot User OAuth Token (xoxb-...)
appToken: "" # App-Level Token (xapp-...) for Socket Mode
allowedChannels: [] # required — empty = deny all channels (secure by default)
allowedUsers: [] # empty = allow all users
# allowAllChannels/allowAllUsers: same auto-infer logic as discord
allowedChannels: [] # empty + no allowAllChannels → allow all (auto-inferred)
allowedUsers: [] # empty + no allowAllUsers → allow all (auto-inferred)
# allowBotMessages: "off" (default) | "mentions" | "all"
allowBotMessages: "off"
# trustedBotIds: Bot User IDs (U...) — find via Slack UI: click bot profile → Copy member ID
Expand Down
14 changes: 10 additions & 4 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["1234567890"] # required — empty or omitted = deny all channels (secure by default)
# allowed_users = ["<YOUR_DISCORD_USER_ID>"] # empty or omitted = allow all users
# allow_all_channels = true # true = allow all channels; false = only allowed_channels
# # omitted = auto-detect from list (non-empty → false, empty → true)
allowed_channels = ["1234567890"] # ↑ omitted + non-empty list → auto-detected as false
# allow_all_users = true # true = any user; false = only allowed_users
# # omitted = auto-detect from list (non-empty → false, empty → true)
# allowed_users = ["<YOUR_DISCORD_USER_ID>"] # ↑ omitted + empty/absent list → auto-detected as true
# allow_bot_messages = "off" # "off" (default) | "mentions" | "all"
# "mentions" is recommended for multi-agent collaboration
# trusted_bot_ids = [] # empty = any bot (mode permitting); set to restrict
Expand All @@ -14,8 +18,10 @@ allowed_channels = ["1234567890"] # required — empty or omitted = deny a
# [slack]
# bot_token = "${SLACK_BOT_TOKEN}" # Bot User OAuth Token (xoxb-...)
# app_token = "${SLACK_APP_TOKEN}" # App-Level Token (xapp-...) for Socket Mode
# allowed_channels = ["C0123456789"] # required — empty or omitted = deny all channels (secure by default)
# allowed_users = ["U0123456789"] # empty or omitted = allow all users
# allow_all_channels = true # true (default) = allow all channels; false = only allowed_channels
# allowed_channels = ["C0123456789"] # only checked when allow_all_channels = false
# allow_all_users = true # true (default) = any user; false = only allowed_users
# allowed_users = ["U0123456789"] # only checked when allow_all_users = false
# allow_bot_messages = "off" # "off" (default) | "mentions" | "all"
# trusted_bot_ids = [] # empty = any bot (mode permitting); set to restrict
# allow_user_messages = "involved" # "involved" (default) | "mentions"
Expand Down
7 changes: 3 additions & 4 deletions docs/discord.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Complete guide to setting up, configuring, and running OpenAB with Discord.
```toml
[discord]
bot_token = "${DISCORD_BOT_TOKEN}"
allowed_channels = ["123456789"] # channel ID allowlist (empty = deny all)
allowed_channels = ["123456789"] # channel ID allowlist (empty = all)
allowed_users = ["987654321"] # user ID allowlist (empty = all)
allow_bot_messages = "off" # off | mentions | all
allow_user_messages = "involved" # involved | mentions
Expand All @@ -75,12 +75,11 @@ trusted_bot_ids = [] # bot user IDs allowed through (empty = an

| `allowed_channels` | `allowed_users` | Result |
|---|---|---|
| empty | empty | **No channels, no users — bot ignores all messages** |
| empty | empty | All users, all channels (default) |
| set | empty | Only these channels, all users |
| empty | set | **Bot ignores all messages** (channels must be configured first) |
| empty | set | All channels, only these users |
| set | set | **AND** — must be in allowed channel AND allowed user |

- Empty `allowed_channels` = bot will not respond anywhere (secure by default)
- Empty `allowed_users` (default) = no user filtering
- Denied users get a 🚫 reaction and no reply

Expand Down
14 changes: 1 addition & 13 deletions docs/slack-bot-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,10 @@ Add the `[slack]` section to your `config.toml`:
[slack]
bot_token = "${SLACK_BOT_TOKEN}"
app_token = "${SLACK_APP_TOKEN}"
allowed_channels = ["C0123456789"] # required — empty = deny all channels (secure by default)
allowed_channels = [] # empty = allow all channels
# allowed_users = ["U0123456789"] # empty = allow all users
```

### Access control behavior

| `allowed_channels` | `allowed_users` | Result |
|---|---|---|
| empty | empty | **No channels, no users — bot ignores all messages** |
| set | empty | Only these channels, all users |
| empty | set | **Bot ignores all messages** (channels must be configured first) |
| set | set | **AND** — must be in allowed channel AND allowed user |

- Empty `allowed_channels` = bot will not respond anywhere (secure by default)
- Empty `allowed_users` (default) = no user filtering

Set the environment variables:

```bash
Expand Down
18 changes: 18 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ fn default_stt_base_url() -> String { "https://api.groq.com/openai/v1".into() }
#[derive(Debug, Deserialize)]
pub struct DiscordConfig {
pub bot_token: String,
/// Explicit flag: true = allow all channels, false = check allowed_channels list.
/// When not set, auto-detected: non-empty list → false, empty list → true.
pub allow_all_channels: Option<bool>,
/// Explicit flag: true = allow all users, false = check allowed_users list.
/// When not set, auto-detected: non-empty list → false, empty list → true.
pub allow_all_users: Option<bool>,
#[serde(default)]
pub allowed_channels: Vec<String>,
#[serde(default)]
Expand Down Expand Up @@ -128,6 +134,12 @@ impl<'de> Deserialize<'de> for AllowUsers {
pub struct SlackConfig {
pub bot_token: String,
pub app_token: String,
/// Explicit flag: true = allow all channels, false = check allowed_channels list.
/// When not set, auto-detected: non-empty list → false, empty list → true.
pub allow_all_channels: Option<bool>,
/// Explicit flag: true = allow all users, false = check allowed_users list.
/// When not set, auto-detected: non-empty list → false, empty list → true.
pub allow_all_users: Option<bool>,
#[serde(default)]
pub allowed_channels: Vec<String>,
#[serde(default)]
Expand Down Expand Up @@ -265,6 +277,12 @@ impl Default for ReactionTiming {

// --- loading ---

/// Resolve an allow_all flag: if explicitly set, use it; otherwise infer from the list.
/// Non-empty list → false (respect the list), empty list → true (allow all).
pub fn resolve_allow_all(flag: Option<bool>, list: &[String]) -> bool {
flag.unwrap_or(list.is_empty())
}

fn expand_env_vars(raw: &str) -> String {
let re = Regex::new(r"\$\{(\w+)\}").unwrap();
re.replace_all(raw, |caps: &regex::Captures| {
Expand Down
13 changes: 6 additions & 7 deletions src/discord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ impl ChatAdapter for DiscordAdapter {

pub struct Handler {
pub router: Arc<AdapterRouter>,
pub allow_all_channels: bool,
pub allow_all_users: bool,
pub allowed_channels: HashSet<u64>,
pub allowed_users: HashSet<u64>,
pub stt_config: SttConfig,
Expand Down Expand Up @@ -234,11 +236,8 @@ impl EventHandler for Handler {
}).clone();

let channel_id = msg.channel_id.get();
if self.allowed_channels.is_empty() {
debug!("allowed_channels is empty — ignoring message in channel {}", channel_id);
return;
}
let in_allowed_channel = self.allowed_channels.contains(&channel_id);
let in_allowed_channel =
self.allow_all_channels || self.allowed_channels.contains(&channel_id);

let is_mentioned = msg.mentions_user_id(bot_id)
|| msg.content.contains(&format!("<@{}>", bot_id));
Expand Down Expand Up @@ -298,7 +297,7 @@ impl EventHandler for Handler {
let (in_thread, bot_owns_thread) = if !in_allowed_channel {
match msg.channel_id.to_channel(&ctx.http).await {
Ok(serenity::model::channel::Channel::Guild(gc)) => {
let parent_allowed = gc
let parent_allowed = self.allow_all_channels || gc
.parent_id
.is_some_and(|pid| self.allowed_channels.contains(&pid.get()));
let owned = gc.owner_id.is_some_and(|oid| oid == bot_id);
Expand Down Expand Up @@ -387,7 +386,7 @@ impl EventHandler for Handler {
}
}

if !self.allowed_users.is_empty() && !self.allowed_users.contains(&msg.author.id.get()) {
if !self.allow_all_users && !self.allowed_users.contains(&msg.author.id.get()) {
tracing::info!(user_id = %msg.author.id, "denied user, ignoring");
let msg_ref = discord_msg_ref(&msg);
let _ = adapter.add_reaction(&msg_ref, "🚫").await;
Expand Down
20 changes: 16 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,14 @@ async fn main() -> anyhow::Result<()> {

// Spawn Slack adapter (background task)
let slack_handle = if let Some(slack_cfg) = cfg.slack {
if slack_cfg.allowed_channels.is_empty() {
warn!("no allowed_channels configured for Slack — bot will not respond to any messages. Add at least one channel ID to [slack] allowed_channels.");
let allow_all_channels = config::resolve_allow_all(slack_cfg.allow_all_channels, &slack_cfg.allowed_channels);
let allow_all_users = config::resolve_allow_all(slack_cfg.allow_all_users, &slack_cfg.allowed_users);
if !allow_all_channels && slack_cfg.allowed_channels.is_empty() {
warn!("allow_all_channels=false with empty allowed_channels for Slack — bot will deny all channels");
}
info!(
allow_all_channels,
allow_all_users,
channels = slack_cfg.allowed_channels.len(),
users = slack_cfg.allowed_users.len(),
allow_bot_messages = ?slack_cfg.allow_bot_messages,
Expand All @@ -128,6 +132,8 @@ async fn main() -> anyhow::Result<()> {
if let Err(e) = slack::run_slack_adapter(
slack_cfg.bot_token,
slack_cfg.app_token,
allow_all_channels,
allow_all_users,
slack_cfg.allowed_channels.into_iter().collect(),
slack_cfg.allowed_users.into_iter().collect(),
slack_cfg.allow_bot_messages,
Expand All @@ -149,14 +155,18 @@ async fn main() -> anyhow::Result<()> {

// Run Discord adapter (foreground, blocking) or wait for ctrl_c
if let Some(discord_cfg) = cfg.discord {
let allow_all_channels = config::resolve_allow_all(discord_cfg.allow_all_channels, &discord_cfg.allowed_channels);
let allow_all_users = config::resolve_allow_all(discord_cfg.allow_all_users, &discord_cfg.allowed_users);
let allowed_channels =
parse_id_set(&discord_cfg.allowed_channels, "discord.allowed_channels")?;
if allowed_channels.is_empty() {
warn!("no allowed_channels configured for Discord — bot will not respond to any messages. Add at least one channel ID to [discord] allowed_channels.");
if !allow_all_channels && allowed_channels.is_empty() {
warn!("allow_all_channels=false with empty allowed_channels for Discord — bot will deny all channels");
}
let allowed_users = parse_id_set(&discord_cfg.allowed_users, "discord.allowed_users")?;
let trusted_bot_ids = parse_id_set(&discord_cfg.trusted_bot_ids, "discord.trusted_bot_ids")?;
info!(
allow_all_channels,
allow_all_users,
channels = allowed_channels.len(),
users = allowed_users.len(),
trusted_bots = trusted_bot_ids.len(),
Expand All @@ -167,6 +177,8 @@ async fn main() -> anyhow::Result<()> {

let handler = discord::Handler {
router,
allow_all_channels,
allow_all_users,
allowed_channels,
allowed_users,
stt_config: cfg.stt.clone(),
Expand Down
18 changes: 11 additions & 7 deletions src/slack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ const MAX_CONSECUTIVE_BOT_TURNS: usize = 10;
pub async fn run_slack_adapter(
bot_token: String,
app_token: String,
allow_all_channels: bool,
allow_all_users: bool,
allowed_channels: HashSet<String>,
allowed_users: HashSet<String>,
allow_bot_messages: AllowBots,
Expand Down Expand Up @@ -552,6 +554,8 @@ pub async fn run_slack_adapter(
true,
&adapter,
&bot_token,
allow_all_channels,
allow_all_users,
&allowed_channels,
&allowed_users,
&stt_config,
Expand Down Expand Up @@ -707,6 +711,8 @@ pub async fn run_slack_adapter(
is_dm,
&adapter,
&bot_token,
allow_all_channels,
allow_all_users,
&allowed_channels,
&allowed_users,
&stt_config,
Expand Down Expand Up @@ -777,6 +783,8 @@ async fn handle_message(
strip_mentions: bool,
adapter: &Arc<SlackAdapter>,
bot_token: &str,
allow_all_channels: bool,
allow_all_users: bool,
allowed_channels: &HashSet<String>,
allowed_users: &HashSet<String>,
stt_config: &SttConfig,
Expand All @@ -803,17 +811,13 @@ async fn handle_message(
};
let thread_ts = event["thread_ts"].as_str().map(|s| s.to_string());

// Check allowed channels (empty = deny all)
if allowed_channels.is_empty() {
tracing::debug!("allowed_channels is empty — ignoring message in channel {}", channel_id);
return;
}
if !allowed_channels.contains(&channel_id) {
// Check allowed channels
if !allow_all_channels && !allowed_channels.contains(&channel_id) {
return;
}

// Check allowed users — skip for bot messages (they go through trusted_bot_ids instead)
if !is_bot_msg && !allowed_users.is_empty() && !allowed_users.contains(&user_id) {
if !is_bot_msg && !allow_all_users && !allowed_users.contains(&user_id) {
tracing::info!(user_id, "denied Slack user, ignoring");
let msg_ref = MessageRef {
channel: ChannelRef {
Expand Down
Loading