SemiLayerDocs

Permissions in chat channels

ChatOps treats every command as if you ran it from the SemiLayer Console. The bot is not a service account — it carries your identity, your role, and your access. This page explains what that means in shared channels, why some commands post privately by default, and how to think about over-share risk.

The mental model — kubectl in a shared terminal

If you've used kubectl get in a shared terminal, you already know the model:

  • The command runs as you. Your RBAC applies.
  • Other people in the room can see what you run and what comes back.
  • You're responsible for what gets shared.

ChatOps is the same. /semilayer search refund policy runs as your linked SemiLayer identity. The platform service does the RBAC check (lens visibility, org membership, environment scope). If you can see the lens, you can search it from chat — and if you post the result publicly, everyone in the channel sees what you saw.

ℹ️

ChatOps is not a per-channel RBAC layer. Other channel members are not consulted before a search runs. The invoker's permissions decide what the bot returns; the invoker decides whether to post that result publicly.

Three layers of identity

LayerRequired forSet by
Workspace installAnything to work at allOrg admin (one-time)
chat_user_linksAny read/notify commandEach user (/semilayer login)
members.roleMutations (notify on/off, here set)Org owner/admin

Read commands (search, similar, query, feed, status, config show, here show) work for any linked org member.

Mutations (notify on/off/all/none, here set, here clear, config set/clear) require role-based permission:

  • notify on/off/all/none → owner or admin (channel subscriptions are an admin lever)
  • here set/clear → owner or admin (channel scope is an admin lever)
  • config set/clear → any linked org member (your own defaults)

The bot returns a clean ephemeral error when a permission gate fails — never a stack trace, never a partial mutation.

Ephemeral by default in shared channels

Read command results post ephemerally by default — only the invoker sees them. This is the safety net for the "I forgot who's in this channel" problem:

# In #general (50 members), Alice runs:
/semilayer search customer churn metrics

# Only Alice sees the response in her chat client. The rest of
# the channel sees nothing.

To share the result with the channel, opt in:

/semilayer search customer churn metrics --public

--public posts to the channel as a normal message. Use it when you mean to share. Don't use it when you don't.

DMs are not "shared" — responses there post normally without needing --public.

Notifications (the /notify-subscribed kind, fired by the platform when ingest fails or a quota crosses) are unaffected by ephemeral defaults. Subscribed channels post visibly because the channel itself opted in.

What about other channel members?

Other members of the channel are not consulted in any RBAC check. The bot doesn't know who's listening. This is intentional:

  • Per-channel RBAC ("everyone in #public-help can search this lens") is a different product. Adding it would mean syncing Slack/Discord channel membership into SemiLayer ACLs in real time, which is fragile and doesn't model the security primitives most orgs already use.
  • The right tool for "this channel can see this data" is to make every channel member a SemiLayer org member with the appropriate role, then trust the platform's RBAC.
  • Until then: ephemeral-by-default + the explicit --public opt-in keeps accidental over-share from happening.

What gets logged

Workspace installs + revokes are audit-logged today (admin_audit_log table). The Phase 5 admin surface adds:

  • chat.channel_scope_set / chat.channel_scope_cleared
  • chat.subscription_added / chat.subscription_removed
  • chat.user_linked / chat.user_unlinked
  • chat.command_invoked (sampled) and chat.public_post (full)

Post-hoc detection is supported by audit logs. Pre-hoc enforcement of "who can see what in this channel" is not in scope.

FAQ for security teams

Can a Slack admin who isn't a SemiLayer member see SemiLayer data through the bot? No. They have to run /semilayer login first, which OAuth-links them to a SemiLayer identity that an org owner has explicitly invited. Without that link, every read command returns "Run /semilayer login first."

Can a developer-role member subscribe a channel to notifications they shouldn't see? No. notify on/off/all/none requires owner or admin role on the org bound to the workspace. Developers + viewers can run notify list and notify types (read-only) but not change subscriptions.

Can the bot post messages without an explicit user action? The bot posts when:

  1. A linked admin runs a command and chooses to post the result publicly (--public).
  2. A platform notification matches a chat_subscriptions row that an admin previously created (the opt-in promise).
  3. A future /watch command (Phase 4B) fires on a schedule the creator set up.

The bot never posts based on internal heuristics, never advertises itself, never DMs unprompted.

Can a workspace see another workspace's data? No. Each chat_workspaces row is bound to one SemiLayer org. The bot mints its platform JWT with the linked user's full org membership claim, and the platform service's RBAC plugin enforces that the requested scope matches a real membership. Cross-org leakage requires a bug at the platform-service layer, not the chatops layer.

Where do bot tokens live? Encrypted at rest via @semilayer/crypto (per-org KMS envelope). The chatops service decrypts on demand only when posting outbound messages. They never appear in API responses, logs, or admin UIs.