mentionable.dev

Slack Identity ACL Cookbook

Audience: Agent authors who want to allow a Slack workspace or Slack user without adding Connector-specific setup UI.

Slack identity is carried as verified IdentityEvidence on NormalizedMessage.sender.identities. Authorization checks must use sender.identities, not sender.profile.

sender.profile is presentation context for humans and LLM attribution: display names, usernames, avatars, locale, timezone, and whitelisted provider facts. It may be projected from verified evidence, but it is still not the principal. Do not authorize from sender.profile.provider_subject, sender.profile.extensions.slack.team_id, usernames, display names, or email hints.

Trust Then Authorize

There are two policy layers:

  1. Trusted Connector issuer policy is runtime/operator policy. It decides whether this receiver trusts a Connector Instance, method, assurance class, and subject namespace enough to verify forwarded attestations and attach them to sender.identities.
  2. Per-agent authorization is agent code or prompt policy. It decides whether the verified Slack subject is allowed to perform this agent action.

In other words, a Connector signature answers “did this Connector issue the claim?”, trusted issuer policy answers “may this runtime rely on that Connector for Slack workspace-member claims?”, and the agent ACL answers “is slack:T123/U456 allowed here?”.

A typical trusted issuer entry for the Slack Connector looks like:

[
  {
    "issuer": "mentionable-slack.example",
    "methods": ["urn:mentionable:auth:slack-workspace-member:v0.1"],
    "assurance": ["platform"],
    "subject_prefixes": ["slack:"]
  }
]

An operator may narrow subject_prefixes to ["slack:T123/"] if the runtime should only accept one workspace from that Connector. Even then, the agent should keep its own per-action ACL in code or system prompt.

Workspace ACL

This check allows any verified member of Slack workspace T123. The subject prefix is slack:T123/.

import type { NormalizedMessage } from '@mentionable/core'

const SLACK_WORKSPACE_MEMBER_METHOD = 'urn:mentionable:auth:slack-workspace-member:v0.1'
const TRUSTED_SLACK_CONNECTOR = 'mentionable-slack.example'
const ALLOWED_WORKSPACE_PREFIX = 'slack:T123/'

export function isAllowedSlackWorkspace(sender: NormalizedMessage['sender']): boolean {
  return hasSlackIdentity(sender, (subject) => subject.startsWith(ALLOWED_WORKSPACE_PREFIX))
}

function hasSlackIdentity(
  sender: NormalizedMessage['sender'],
  subjectMatches: (subject: string) => boolean,
): boolean {
  return (sender.identities ?? []).some(
    (identity) =>
      identity.issuer === TRUSTED_SLACK_CONNECTOR &&
      identity.method === SLACK_WORKSPACE_MEMBER_METHOD &&
      identity.assurance === 'platform' &&
      subjectMatches(identity.subject),
  )
}

Exact User ACL

This check allows only Slack user U456 in workspace T123. The exact subject is slack:T123/U456.

import type { NormalizedMessage } from '@mentionable/core'

const SLACK_WORKSPACE_MEMBER_METHOD = 'urn:mentionable:auth:slack-workspace-member:v0.1'
const TRUSTED_SLACK_CONNECTOR = 'mentionable-slack.example'
const ALLOWED_SLACK_USER = 'slack:T123/U456'

export function isAllowedSlackUser(sender: NormalizedMessage['sender']): boolean {
  return (sender.identities ?? []).some(
    (identity) =>
      identity.issuer === TRUSTED_SLACK_CONNECTOR &&
      identity.method === SLACK_WORKSPACE_MEMBER_METHOD &&
      identity.assurance === 'platform' &&
      identity.subject === ALLOWED_SLACK_USER,
  )
}

These snippets assume the receiving runtime only attaches Connector-issued evidence after verifying the attestation signature, audience, freshness, and Trusted Connector issuer policy. The explicit issuer, method, and assurance checks make the agent’s authorization intent clear and keep the ACL from accidentally accepting a different identity source.