#3

feat: prototype native Slack plugin over Connect

DraftPatrick-Erichsenwants to merge intofrom

CAUTION

Proof of concept only — do not merge, ship, or deploy as production architecture.

This draft demonstrates one narrow native Slack vertical slice and intentionally retains security, lifecycle, and coverage gaps. Companion OpenClaw draft: openclaw/openclaw#123963.

Relationship to current main

vercel-labs/vercel-openclaw#2 is merged and remains the default behavior on main: the Vercel host owns Slack policy/session/reply behavior and may invoke openclaw agent.

This PR changes behavior only when both native host-bridge variables are configured. In that PoC mode:

Connect -> verified raw Slack envelope -> native OpenClaw /slack/events -> channels.slack.* policy/session/thread pipeline -> host Slack API proxy -> Connect getToken() -> Slack

Only native PoC mode bypasses generic CLI invocation. Default mode remains unchanged.

What the PoC adds

  • Connect verifies Slack; /api/slack preserves the complete raw body and forwards it with a narrow host assertion.
  • OpenClaw's native Slack plugin remains authoritative for allowlists, activation, sessions, threading, dedupe, reactions, lookups, and replies.
  • /api/slack-proxy/{method} authenticates the sandbox, calls @vercel/connect.getToken(), strips sandbox token overrides, and calls Slack. The Slack token never enters the VM.
  • Direct OpenAI model access is separately brokered: the host key is injected by the Vercel firewall only for api.openai.com. The VM stores only the exact nonsecret sentinel sk-vercel-firewall-brokered-placeholder-not-a-real-key required to pass OpenClaw's provider-key validation.
  • Streaming is disabled because several token-only/fresh-client paths are not yet bridged.
  • Ingress/proxy logs record event IDs, raw-body hashes, methods, Connect token source, Slack status, and request IDs without payloads, message text, or credentials.

The sandbox still receives a shared PoC host-bridge bearer. It is not a Slack credential; production needs scoped workload identity or signed assertions.

Live evidence

Tested in the Local Claw workspace, private connect-poc-test channel, with the Connect-installed openclaw-poc app.

Native policy denial

Two verified events (Ev0BQSBVQE21, Ev0BQPRL8F5Z) entered the native plugin. Its configured channels.slack.* policy denied the user after native auth.test, conversations.info, and users.info; it posted chat.postEphemeral, made no model call, and started no generic agent process. All Slack operations used tokenSource=connect and returned Slack HTTP 200.

This is the important configuration proof: Connect did not reimplement or override the OpenClaw allowlist decision.

Successful native model round trip

User mention parent: 1787028584.664219

  • Connect accepted the corresponding native app mention as Ev0BQSH9AP29, HTTP 200, raw-body SHA-256 8f10d3360de38da414e684af1f44a011ec872bb398f4f7b09796d42f3b16822e.
  • OpenClaw logged native session key agent:main:slack:channel:c0bqckh5h1c:thread:1787028584.664219.
  • Native WebClient operations auth.test, conversations.info, users.info, reactions.add, assistant.threads.setStatus, and chat.postMessage traversed the host proxy with tokenSource=connect; Slack returned HTTP 200.
  • OpenClaw's built-in runtime called https://api.openai.com/v1/responses with openai/gpt-5.4; the firewall-injected host credential produced HTTP 200.
  • Slack visibly rendered the native eyes reaction and exactly one threaded model reply: CONNECT POC OK.
  • Native mode did not invoke openclaw agent.

The host logs the exact received raw-body hash; unit coverage separately proves that those bytes are forwarded unchanged into native /slack/events.

Final sandbox boundary verifier

{
  "slackCredentialsAbsent": true,
  "modelCredentialsAbsent": true,
  "modelPlaceholderIsExactSentinel": true,
  "modelPlaceholderDiffersFromHostKey": true,
  "modelPlaceholderExactMatchCount": 1,
  "nodeCompileCacheExcludedAsCodeArtifact": true,
  "genericAgentProcessCount": 0,
  "config": {
    "configuredPolicyFieldCount": 1,
    "streamingDisabled": true,
    "hostBridgeApiUrlConfigured": true,
    "hostBridgeAuthIsEnvSecretRef": true
  },
  "slackPluginEnabled": true
}

The verifier scans command/process env, argv, config, all OpenClaw state, and non-compile-cache /tmp files. Node's compile cache is excluded explicitly because it contains bytecode copies of immutable source credential fixtures/patterns, not runtime state.

Validation

  • Host tests: 132/132 passed.
  • ESLint, TypeScript, git diff --check, and local Next production build passed.
  • Vercel production build passed for deployment dpl_F8RQoqGan92VLMhwLeQfGYiNx2ct.
  • Direct firewall probe: OpenAI HTTP 200; sandbox model credentials absent; exact sentinel differs from host key.
  • Companion OpenClaw proof: 151 focused Slack tests plus extension typecheck/static gates passed.

Known gaps / do not merge until resolved

  • Private Slack downloads bypass the proxy; upload API calls may proxy while upload bytes go to absolute Slack upload URLs.
  • Detached/proactive sends, directory/actions/probes, and other fresh WebClient paths need one bridge-aware client factory.
  • Bolt response_url, interactive actions, and slash commands are not end-to-end bridged.
  • Streaming remains disabled; absolute Slack URLs need an explicit broker contract.
  • The host proxy needs a method allowlist and tighter request/response bounds.
  • Connect token refresh/revocation, Slack retries/rate limits, replay protection, and durable dedupe need explicit contracts.
  • Multi-workspace/install routing needs connector/installation identity, not one fixed connector.
  • The host acknowledges before durable sandbox admission; a later failure can still lose an event.
  • The shared host assertion needs connector/install scoping, rotation, expiry, and replay protection.
  • Activity/dedupe stores are in-memory in this Hobby deployment; idle suspension is nonfunctional across serverless functions.
  • Sandbox timeout extension hits the Hobby ceiling and the gateway can briefly drain/retry during cold restart; lifecycle needs production design.
  • Live host and VM do not yet log a shared raw-envelope correlation hash; byte equality is currently code/unit proven plus host-side live hash.

Please keep this PR in draft. Do not mark ready, merge, or release it.