OpenShell Slack Admin Bridge
The OpenShell Slack Admin Bridge brings OpenShell sandbox network-egress approvals into Slack.
When a sandboxed agent hits an egress denial, OpenShell proposes a draft network-policy change (a "chunk"). The bridge surfaces each pending chunk as an interactive Slack message so an admin can approve or reject it. Approvals merge into that sandbox's policy and hot-reload, and the sandbox's /wait loop retries. Rejections leave the sandbox denied.
The bridge publishes an App Home tab for admins, refreshed on app_home_opened. It shows the pending-approval queue rendered as action cards, plus a chart of recent decision activity, with cards deep-linking to the original request message.

Understanding the flow
OpenShell gateway <--gRPC--> Bridge <--Socket Mode--> Slack
(draft chunks) (poll + decide) (admins)
- The bridge polls
GetDraftPolicyper sandbox. - A newly discovered pending chunk is posted to the routed channel as a Block Kit message.
- An admin clicks Approve (with a confirmation) or Reject (which opens a reason modal).
- The bridge calls
ApproveDraftChunk(with the chunk'sreview_token) orRejectDraftChunk, then rewrites the message to a terminal state noting who decided and the new policy version. - Decisions made outside Slack are detected on the next poll, and the message is closed.
To guard against stale approvals, ApproveDraftChunk carries a review_token. A stale token returns FAILED_PRECONDITION, so the bridge refreshes the token and retries once. A per-chunk lock prevents two admins from double-deciding, and the bridge stores the Slack approver and message coordinates in a durable state file keyed by chunk_id so decisions survive restarts.
Filesystem and process policy are locked at sandbox creation and are not proposable, so they are out of scope by design.
1. Create a Slack app
The bridge uses Socket Mode, so it needs no public request URL. The app opens a single WebSocket connection to Slack and requests a single bot scope, chat:write, so admins can approve the install without granting broad permissions.
Click the button below to create the app from a pre-configured manifest, then follow the steps in App Settings.
2. Configure tokens
Socket Mode needs two tokens: an app-level token and a bot token.
Grab an app-level token
- In your app's settings, go to Basic Information.
- Scroll to the App-Level Tokens section and click Generate Token and Scopes.
- Name the token (for example, "OpenShell") and add the
connections:writescope. - Click Generate and copy the
xapp-token.
Grab a bot token
- In your app's settings, go to OAuth & Permissions.
- Under OAuth Tokens, click Install to Workspace.
- After being returned to OAuth & Permissions, copy the Bot User OAuth Token (
xoxb-).
3. Invite the bot to your approval channel
The bot does not self-join channels, since that would require the channels:join scope. Create the channel where approvals should appear, then invite the bot:
/invite @openshell_admin
You'll add this channel's ID to config/admins.yaml in the next step.
4. Configure the bridge
Copy the example config files:
cp .env.example .env
cp config/admins.example.yaml config/admins.yaml
Edit .env with your Slack tokens, gateway URL, and authentication mode. Edit config/admins.yaml with your admin allow-list and channel routing.
Never commit .env or config/admins.yaml.
The bridge connects to the OpenShell gateway with one of two authentication modes.
| Authentication mode | Instructions |
|---|---|
mtls (the single-host default). | Point OPENSHELL_CA_CERT, OPENSHELL_CLIENT_CERT, and OPENSHELL_CLIENT_KEY at the install bundle under ~/.config/openshell/gateways/openshell/mtls/. |
bearer (for Docker, Helm, or Kubernetes). | Set OPENSHELL_BEARER_TOKEN or OPENSHELL_BEARER_TOKEN_FILE. |
5. Run the bridge
npm install
npm run dev # watch mode against a real gateway
npm start # compiled (after npm run build)
Local testing with the mock gateway
The repo ships a mock gateway that implements the OpenShell RPC subset in memory, with a seeded sandbox and pending chunks that honor review_token. Use it to try the flow without a real gateway.
# Terminal 1: mock gateway (insecure loopback)
npm run dev:mock
# Terminal 2: bridge pointed at the mock
# set these in .env first:
# OPENSHELL_GATEWAY_URL=127.0.0.1:17670
# OPENSHELL_USE_TLS=false
npm run dev
The mock seeds two pending chunks and injects a late proposal about 12 seconds after start, so you can watch the poller pick it up.
Audit event capture
Alongside the approval bridge, an optional audit sink streams OpenShell's OCSF audit events into a private Slack channel. It runs as a separate process (npm run start:capture, or npm run dev:capture in watch mode), independent of the approve/reject bridge. Its only outward call is chat.postMessage to a dedicated audit channel.
Enable it by setting CAPTURE_SOURCES (comma-separated; empty turns it off) and a routing.audit_channel in the YAML that differs from every approval channel. Invite the bot to that channel, since it holds only chat:write and cannot self-join.
There are two capture sources.
| Capture source | Use | Instructions |
|---|---|---|
file | Tails the OCSF JSONL file OpenShell writes locally. | Set CAPTURE_FILE_PATH (a glob works for daily-rotated logs). Byte offsets persist to CAPTURE_FILE_OFFSET_STATE, so a restart resumes without replaying the backlog. |
http | Runs an inbound receiver for an external log-shipper (Filebeat, Vector, Fluent Bit) to POST to. | Set CAPTURE_RECEIVER_TOKEN (a shared bearer the receiver requires) and optionally CAPTURE_RECEIVER_BIND (default 0.0.0.0:8090). Set CAPTURE_RECEIVER_TLS_CERT and CAPTURE_RECEIVER_TLS_KEY to serve HTTPS. |
To silence specific noise, set capture.exclude_event_types in the YAML (an exact, case-insensitive match on the OCSF class or type name). Posts flow through a bounded, rate-limited queue.
Next steps
Your OpenShell Slack Admin Bridge is ready to route egress-policy approvals into Slack. From here:
- Explore the OpenShell project.
- Review the full deployment and testing runbooks in the sample repository.