These pages describe PrivacyFence 7.0.2. Download it · What changed
Slack setup¶
PrivacyFence uses a Slack user token: your AI client sees exactly the channels, direct messages and private groups you can see, as you. There is no bot to invite.
You need this page only to use your organization's own app instead of the built-in one. The next two sections say why you might, and what the built-in app does.
The built-in app, and limited mode¶
An organization server and the Docker image offer Slack with PrivacyFence's built-in app: nothing to register, and each person approves it in their browser from PrivacyFence. If your workspace requires admin approval for apps, a workspace admin approves it once on Slack's side.
Slack limits apps that are not listed in the Slack Marketplace to one history or thread request a minute and 15 messages a call. PrivacyFence is not listed yet, so the built-in app runs in limited mode:
slack_get_channel_historyreads through Slack search instead. Some message subtypes are missing, a message posted a moment ago can take a while to appear, and thread replies come back as top-level matches. The result carries a note, "Read through Slack search, because Slack limits history reads for this app; an administrator can switch to your workspace's own app on the Admin page.", and so does the tool description. For a conversation search cannot be scoped to, it falls back to the history call and says why.slack_get_thread_repliesreturns at most 15 replies and names the limit in a note.- Everything else (search, sending, read state) is unaffected.
The built-in Slack row on Admin → Services explains the limit. Until PrivacyFence is listed, use your workspace's own internal app, which reads at full speed.
Because Slack sends no PKCE, a sign-in code for the built-in app passes through
oauth.privacyfence.eu without it; your own app avoids this. See
Security and compliance.
Do not mark the built-in PrivacyFence app as trusted or pre-approved: that trust would extend to every PrivacyFence server and to anyone using the public app. To trust an app, register your own.
Your workspace's own app, in one click¶
On an organization server, Admin → Services has a Create your workspace's own Slack app
button on the Slack row. It opens Slack's create page with a manifest filled in for your server (the
redirect URL <server>/oauth/callback/slack and the user token scopes below, no bot). If Slack does
not fill the page in, choose Create New App → From an app manifest and paste the manifest shown
under If Slack does not fill in the app. Then:
- Leave the app undistributed. It is then an internal app, which serves one workspace and keeps Slack's full rate limits. Never activate public distribution (Manage Distribution → Activate Public Distribution): Slack would then apply the Marketplace limits to it.
- Copy the Client ID and Client Secret from Basic Information → App Credentials.
- Sign them as a connector config and upload the file on the same row (Upload a connector
config…):
python3 scripts/sign_connector_config.py sign --provider slack --serial 1 --client-id <Client ID> --client-secret-file <file> --key <signing key> --output slack.connector.txt, or the signing page that works offline in a browser. The first upload asks you to check the signing key's fingerprint, and a passkey. See org-mode-setup-guide.md.
Switching to your own app, or back with Remove our own app, makes everyone who connected Slack sign in again. An internal app serves one workspace, so people in any other Slack workspace cannot sign in with it.
On a desktop install the app is a slack section in the bundle instead; see below.
What you need¶
- Permission to create apps in your Slack workspace (and, if your workspace requires it, an admin who approves app installs).
- Python 3 to run
scripts/sign_connector_config.py(organization server) orscripts/build_org_bundle.py(desktop install), or a browser for the offline signing page.
Register the app¶
If you did not use the Admin button, register the app by hand:
- Go to api.slack.com/apps → Create New App → From scratch.
Name it (for example
PrivacyFence), pick your workspace and click Create App. - OAuth & Permissions → Scopes → User Token Scopes (not Bot Token Scopes): add every scope in the values table. Add no bot scopes.
- OAuth & Permissions → Redirect URLs: add the redirect for each kind of deployment you run (see Values): the local redirect for desktop installs, the org redirect for an organization server. Click Add, then Save URLs; a URL that was added but not saved is not registered. One app can carry both, and the same client ID and secret serve both kinds of deployment.
- Basic Information → App Credentials: copy the Client ID and Client Secret.
Never activate public distribution (Manage Distribution → Activate Public Distribution),
and never share the connector config or bundle outside your workspace. An app created from scratch
and left undistributed is an internal app. Slack restricts apps distributed outside the Slack
Marketplace to 1 request per minute and 15 messages per call on conversations.history and
conversations.replies, which slack_get_channel_history, slack_get_thread_replies and
single-message lookups (permalinks, thread previews on approval cards) rely on. Nothing fails outright; reads just become very slow and
truncated.
If your workspace requires admin approval for app installs, an admin approves each user's first sign-in on Slack's side. That is a Slack workspace setting, not a PrivacyFence one.
Rate limits and routines¶
Slack limits each method by tier (requests per minute). The methods PrivacyFence's Slack tools call:
| Method | Tier | Used for |
|---|---|---|
conversations.history, conversations.replies |
Tier 3 (50/min); 1/min and 15 messages for an app distributed outside the Marketplace | channel history, thread replies and a thread's read marker |
conversations.info |
Tier 3 (50/min) | channel type, membership and your read marker |
users.conversations |
Tier 3 (50/min) | the conversations you are a member of |
search.messages |
Tier 2 (20/min), at most 100 matches per page | search |
conversations.list |
Tier 2 (20/min) | the channel, DM and group chat listings |
A routine that reads Slack by time should not read each conversation one by one: that costs a
conversations.history and a conversations.info request per conversation, about 2 minutes for 50
conversations on an internal app and about 50 minutes on a distributed one. Each run instead:
- Calls
slack_search_messageswithminutesset to the time since the last run,member_onlytrue,exclude_channelsset to the ids the "Slack — conversations I'm a member of" rule excludes, andcount100. This costs oneusers.conversationswalk (one request per 200 conversations) and 1–3search.messagesrequests. Addread_statetrue only when the run needs read state for most results: it costs oneconversations.inforequest per conversation in the results. Otherwise callslack_get_read_stateafterwards for the few messages that matter (at most 20, at most 3 threads). - Calls
slack_get_thread_repliesfor a result whose thread needs context. This costs oneconversations.repliesand oneconversations.inforequest. Its replies come back withread_statefrom the thread's own marker. - Uses
slack_get_channel_historywithminutesonly for a conversation where the search-index delay matters.
On an app distributed outside the Marketplace, step 2 is limited to 1 request per minute. See ADR 0227.
Read state¶
read_state is unread, read or unknown, judged against your own Slack read marker for the
conversation. A message you wrote is read. unknown means there is no marker to judge by.
A thread reply in search results is unknown: Slack tracks the read marker of a thread separately
from the channel's, and a search result does not carry it. slack_get_thread_replies and
slack_get_read_state read the thread's own marker.
These tools return it:
slack_get_channel_historyandslack_get_thread_replies, on every message, with no extra request.slack_search_messages, only withread_statetrue.slack_get_read_state, for up to 20 messages you name, and no message text.
Values¶
| Item | Value |
|---|---|
| Local redirect (desktop installs) | http://127.0.0.1:53682/callback — exactly this; Slack requires an exact match |
| Org redirect (organization server) | <server>/oauth/callback/slack, with <server> your server's public address, PRIVACYFENCE_ISSUER_URL (for example https://pf.acme.example.com/oauth/callback/slack) |
| User token scopes | channels:read, groups:read, im:read, mpim:read, channels:history, groups:history, im:history, mpim:history, users:read, users:read.email, search:read, chat:write, im:write, channels:write, groups:write, mpim:write |
| Connector config (organization server) | sign_connector_config.py sign --provider slack --client-id <Client ID> --client-secret-file <file holding the Client Secret> |
| Bundle flags (desktop install) | --slack-client-id <Client ID> and --slack-client-secret <Client Secret> (both required together); optional --slack-scopes <scope> … |
What the less obvious scopes are for: users:read.email resolves authors' email addresses;
im:write and mpim:write open a new direct or group message (slack_create_group_chat);
im:write, channels:write, groups:write and mpim:write mark a conversation unread (the
mark_unread option of slack_send_message), depending on the conversation type.
--slack-scopes replaces the requested scope list with your own, for desktop installs and an
organization server alike. A scope you leave out breaks the tools that need it, so leave it unset
unless you have a reason.
Give PrivacyFence the app¶
On an organization server, sign the Client ID and Client Secret as a connector config and upload it on Admin → Services (see above). On a desktop install, put them in the bundle:
python3 scripts/build_org_bundle.py \
--slack-client-id 1234567890.1234567890 \
--slack-client-secret abcdef0123456789abcdef0123456789 \
-o org_bundle.json --merge
--merge adds Slack to an existing bundle; drop it if this is the first service in the bundle.
Install the bundle on Admin → Organization configuration
(connecting-a-service.md).
Users connect¶
Users click Authenticate… next to Slack (desktop install, after the bundle is installed) or Connect (organization server), then review the permissions on Slack's page and click Allow. The shared flow is described in connecting-a-service.md.
Troubleshooting¶
missing_scope errors — a scope was not on the app when the user signed in. Add it under
User Token Scopes, then have each user Reconnect… to get a token that includes it.
not_in_channel on history reads — the token sees only conversations the user is a member of.
Join the channel in Slack first.
invalid_auth or token_revoked — the token was revoked (the user removed the app, or an
admin uninstalled it). Reconnect….
Slack says redirect_uri did not match any configured URIs, or the browser does not come
back — the URL Slack names after Passed URI: is missing from the app's Redirect URLs. Add
exactly that URL and click Save URLs, then connect again; no bundle rebuild or restart is
needed. It must be exactly http://127.0.0.1:53682/callback for desktop installs, or
<server>/oauth/callback/slack for an organization server; localhost in place of
127.0.0.1 does not match. If the URL is already listed, check that the app you edited is the one
whose Client ID is in the connector config or bundle.
Channel history reads through search, or thread replies stop at 15 — the server uses the built-in app, which Slack limits until PrivacyFence is in the Marketplace. Switch to your workspace's own app (above).
Reads became slow, or channel history stops at 15 messages with your own app — check Manage Distribution. If public distribution was ever activated, Slack may treat the app as distributed outside the Marketplace and apply the reduced limits described under Register the app. Deactivate it; if the limits persist, ask Slack support to confirm the app's distribution status.