#Openclaw guardrail

1 messages · Page 1 of 1 (latest)

woven cave
#

Is it possible to obligate to send a parameter to my message, for example I want openclaw to always send phone number parameter whenevet it calls an agent, is it posdible?

prisma daggerBOT
mortal mangoBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

prisma daggerBOT
worthy mirageBOT
#

Not as a single built-in “required parameter on every agent call” switch, no.

Closest options:

  • Fixed value every time: put it in prompt/context, for example via AGENTS.md or a channel/chat systemPrompt.
  • Dynamic value per message like the sender’s phone number: use a hook/plugin and inject it before prompt build. The relevant field is senderE164 / SenderE164 (best effort).
  • Media model CLI templates only: there is already a {{SenderE164}} template variable, but that is not a universal chat-agent parameter.

Good docs:

So, short version:

  • simple config for all normal agent turns: no
  • possible with prompt injection/hook/plugin: yes
#

If you want, tell me whether you mean:

  1. a hardcoded phone number, or
  2. the current sender’s phone number,

and which channel you use, and I’ll point you to the exact setup.

woven cave
#

Current sender phonr number

worthy mirageBOT
#

Yep, if the channel actually has a phone number.

What you want is:

  • senderE164 in plugin hook metadata
  • SenderE164 in some template/internal contexts

Important catch:

  • WhatsApp / Signal / some phone-based channels: usually yes, best effort
  • Discord: no, there is no sender phone number to pass through

So the real answer is:

  • Current sender phone number on phone-based channels: possible
  • Force it into every normal model call with one config flag: no
  • Do it via hook/plugin that injects senderE164 into prompt/body: yes
woven cave
#

My proxy fallback implemented to route messages received from openclaw by whatsapp for an external of agent of mine follows this approach:
It accomplishes this through a strict, three-layer defense:
​1. Relying Only on Trusted Data
To figure out who is sending the message, the code completely ignores the actual text the user typed. Instead, it looks exclusively at secure, hidden metadata attached to the request (the X-Phone HTTP header or the system-level body.user field). Because the user cannot alter these background channels, the system can reliably extract the real phone number and use it to look up the person's true user_id in the database.
​2. Scrubbing the Chat History
Even though the system knows the real identity, the AI might still get confused if it reads a message containing fake credentials. To prevent this, the code intercepts the chat messages and runs them through a "scrubber" before the AI sees them. It uses regular expressions to hunt down and delete any typed text that looks like whatsapp_phone:, user_id:, tenant:, or manager:.
​3. Issuing a Locked-Down Key
Once the real user is identified, the proxy requests a temporary, cryptographically secure "bearer token" that is hardcoded to that specific user_id. When the proxy finally forwards the scrubbed chat to the AI, it attaches this specific token. This ensures that even if the AI somehow gets tricked into requesting another person's data, the internal systems will reject the request because the token only has permission to view the current user's files.
​The Fallback Mechanism
If the system cannot find a trusted phone number in the background data, it instantly blocks access. In this specific scenario, it will briefly scan the user's typed message to see if they provided a phone number, but it only uses that number to generate a helpful "Please click this link to verify your device" URL. It never uses that typed number to actually log them in.

worthy mirageBOT
#

Honestly, that’s the right pattern.

I’d frame it like this:

  • Do not rely on the model to carry identity
  • Resolve identity from trusted channel metadata (senderE164 / internal sender fields)
  • Enforce authorization with a scoped backend token
  • Treat prompt scrubbing as defense-in-depth, not the security boundary

So yes, your 3-layer approach is solid.

A few important caveats:

  1. X-Phone is only trustworthy if only your proxy can set it
    If that header can come from the public edge, sign it or keep that hop internal only.

  2. Regex scrubbing helps, but it is not real auth

#

Good for reducing prompt confusion, bad as a hard guarantee. Your scoped bearer token is the actual safety boundary.

  1. Fail closed is correct
    If trusted sender metadata is missing, block access.

  2. Keep tokens short-lived and tightly scoped
    Bind to user id, tenant, maybe channel/account too, and give them a short TTL.

  3. Rate-limit the fallback verification flow
    Otherwise typed phone numbers can become an enumeration or spam vector.

So my recommendation is:

  • Don’t try to force OpenClaw/the model to always “send phone number” as a prompt parameter
  • Keep identity outside the model
  • Use trusted metadata + backend-scoped auth, exactly like you described
#

That lines up well with OpenClaw’s security model too, where prompt rules are soft and the real protection comes from hard boundaries. Docs: https://docs.openclaw.ai/gateway/security

If you want, I can help you turn this into a concrete OpenClaw-side design, for example:

  • hook/plugin injection using senderE164
  • proxy contract shape
  • token claims format
woven cave
#

Yeah, but it's not guaranteed that openclaw send to proxy numbers, I can guarantre that with this plugin somehow? Or do I need to move proxy to above openclaw channels instead below it in front of our custom external agent

woven cave
#

Thanks — your caveats were useful. Sanity-check before I commit.

Our proxy today sits below OpenClaw, presenting an OpenAI-compatible /v1/chat/completions. It resolves senderE164 → user_id, mints a short-lived HMAC bearer scoped to that user_id, and the backend rejects any bearer whose user_id doesn't match the path (cross-tenant blocked cryptographically — verified).

Problem: From the LLM position, identity intake is fragile. body.user/X-Phone aren't consistently populated, and prompt-scraped markers are injectable so they can't feed auth.

Two options:

  • (a) Stay below: Only viable if there's an OpenClaw primitive that guarantees senderE164 reaches the LLM endpoint.
  • (b) Move in front: Gateway ingests the webhook first, resolves identity from the payload, stashes conversation_id → scoped bearer, forwards to OpenClaw. Backend looks up the bearer by conversation_id.

Questions:

  1. Is there a supported OpenClaw primitive (hook/plugin/header) piping senderE164 reliably into the downstream model call? If yes, (a) wins.
  2. If I move in front, what commonly breaks — signature verification, media URLs, retries, continuation turns?
  3. Which position is the production norm?
worthy mirageBOT
#

Let me verify the plumbing before I steer you.

#

Sanity check, I’d not bet prod on (a).

1) Is there a supported primitive that reliably pipes senderE164 into the downstream model call?

Short answer: no, not as a first-class turnkey primitive.

What exists today:

  • senderE164 is available in message/plugin hook metadata
  • provider calls support:
    • static provider headers via models.providers.*.headers
    • custom provider hooks like wrapStreamFn and resolveTransportTurnState

What does not exist, as far as the current docs/code show:

  • no built-in x-openclaw-sender-e164 style downstream header
  • no guaranteed automatic forwarding of channel sender identity into the model transport
#
  • no provider hook context that naturally carries senderE164 end-to-end

So option (a) only works if you build a custom bridge yourself. That is possible, but it is bespoke, not a supported primitive.

2) If you move in front, what commonly breaks?

This is the main checklist:

  • Webhook signature verification

    • easiest thing to accidentally break if you reserialize body / lose raw headers
  • Media fetch

    • expiring media URLs, auth headers, cookies, or provider-specific attachment fetch rules
  • Retries / dedupe

    • upstream webhook retries must be idempotent by provider message id
#
  • Session routing

    • this one matters a lot in OpenClaw:
    • WhatsApp DMs default to session.dmScope: "main", which collapses all DMs into one main session unless you change it
    • for multi-user auth, set:
      • session.dmScope: "per-channel-peer" or
      • session.dmScope: "per-account-channel-peer" (better for multi-account)
  • Group chats

    • do not key bearer by bare conversation_id if multiple senders share that conversation
    • OpenClaw’s default group scope is per-sender, which is good, but your bearer cache needs to match that
  • Continuation turns

    • if auth lives only on the ingress webhook, later turns/retries/background continuations may not have the original payload
    • persist auth against the OpenClaw session key, not just the external conversation id

If you go HTTP into Gateway, the supported routing primitive is x-openclaw-session-key. That is much safer than hoping user or prompt content stays aligned.

#

3) Which position is the production norm?

(b), identity in front, almost always.

Production norm is:

  • resolve identity at ingress
  • bind auth to session/tool/backend
  • keep the model transport layer mostly dumb

That matches OpenClaw’s architecture better too. The provider/model boundary is not the right place to discover end-user identity.

My recommendation

I’d ship:

  • (b) move in front
#
  • cache scoped auth by session key, not plain conversation_id
  • set session.dmScope: "per-account-channel-peer"
  • keep group auth keyed per sender/session, not per room only

So, bluntly: don’t commit to “guaranteed senderE164 reaches downstream model proxy” unless you’re willing to own a custom plugin bridge. For prod, ingress-side identity is the cleaner boundary.

Docs I checked:

If you want, I can sketch the exact session-keyed bearer design I’d use here.

woven cave
#

Which WhatsApp provider? Twilio, Meta Cloud API, or something else? The webhook signature scheme differs (Twilio uses X-Twilio-Signature HMAC, Meta uses X-Hub-Signature-256). Our ingress proxy has to
verify the right one.

worthy mirageBOT
#

OpenClaw’s built-in WhatsApp channel is neither Twilio nor Meta Cloud API.

It is:

  • WhatsApp Web via Baileys
  • Gateway owns the linked WhatsApp session/socket directly

So for stock OpenClaw WhatsApp:

  • there is no Twilio webhook signature
  • there is no Meta X-Hub-Signature-256 webhook
  • your “verify inbound webhook signature” step only applies if you built a custom WhatsApp ingress in front of OpenClaw

Docs: https://docs.openclaw.ai/channels/whatsapp

So the clean distinction is:

#
  • Built-in OpenClaw WhatsApp → Baileys socket, no Twilio/Meta webhook auth layer
  • Your custom/provider-managed ingress → verify whatever that upstream provider uses

If you want, I can update the earlier recommendation specifically for the Baileys/OpenClaw-native path, because it changes the ingress picture a bit.

woven cave
#

sure, do that

worthy mirageBOT
#

For the Baileys/OpenClaw-native path, I’d change the recommendation a bit:

Recommended shape

Do not add a separate ingress proxy in front.
There is no Twilio/Meta webhook layer to intercept anyway.

Instead:

  1. Let OpenClaw ingest WhatsApp directly via Baileys
  2. At the Gateway/plugin layer, read trusted sender metadata (senderE164)
  3. Resolve senderE164 -> user_id
  4. Mint/store a short-lived scoped bearer
  5. Bind that bearer to the OpenClaw session key
  6. Have your backend/tooling read auth from that session-side binding, not from prompt text and ideally not from the downstream model transport either

Why this is cleaner

#

With native WhatsApp:

  • OpenClaw already has the trusted channel metadata
  • there is no webhook signature problem to solve
  • the fragile part is only the handoff from channel/session layer to your backend auth
  • so the right boundary is inside OpenClaw, above the model provider

Two important config choices

For this to be safe, I’d set:

{
  session: {
    dmScope: "per-account-channel-peer",
    scope: "per-sender"
#
  }
}

Why:

  • default WhatsApp DM behavior is dmScope: "main", which is too loose for per-user auth
  • per-account-channel-peer isolates direct chats properly
  • scope: "per-sender" keeps group auth from collapsing multiple people into one room-level identity

What I would avoid

I would not:

  • rely on prompt markers
  • rely on the model to echo identity back
#
  • rely on a bare conversation_id -> bearer mapping for groups
  • make the downstream OpenAI-compatible proxy the place where user identity is “discovered”

If you absolutely must keep auth below OpenClaw

Then you’re in custom provider plugin / sidecar lookup territory, not a stock OpenClaw primitive.

That can work, but it’s more brittle than binding auth at the Gateway/session layer.

Bottom line

For native OpenClaw WhatsApp, the production-ish path I’d choose is:

Baileys ingress -> OpenClaw plugin/hook resolves sender -> session-keyed scoped bearer -> backend/tool auth

not

#

Baileys ingress -> prompt/header guessing -> downstream model proxy auth

If you want, I can sketch the exact session-keyed bearer map I’d use for:

  • direct chats
  • group chats
  • bearer TTL / refresh rules
woven cave
#

Im writing a plan by myself, but I need to know specifically:

  1. How plugins are loaded in this self-hosted deployment. Dockerfile + workspace-colmeia/ + workspace-config/ structure suggests there's a workspace manifest wiring plugins in. I need the exact shape — JS module? Python? OpenClaw-native TS?
  2. Current session config in workspace-colmeia — is dmScope already set, or are we relying on the default "main" (which is the loose one)?
  3. Where provider/tool calls are configured — specifically, where Bee gets called from inside OpenClaw. The plugin needs to attach the bearer to exactly that call site (header, request interceptor, or tool-context injection — depends on OpenClaw's plugin API shape).
  4. Does OpenClaw expose a session.set(key, value) + session.get(key) API for plugin-to-tool-call state sharing? That's the natural home for the bearer. If not, we fall back to an external KV keyed by session_key.
woven cave
#

Stock OpenClaw, WhatsApp via Baileys, OpenAI-compatible provider. When a plugin's subagent_spawning hook resolves identity from ctx.requesterSessionKey, what field on OpenClaw's outbound provider request lets the receiving endpoint correlate back to that same session? Header, body field, or something else?

worthy mirageBOT
#

I found the relevant transport hook. One more tiny check: whether that metadata object actually goes on the wire, or whether only the headers do.

#

Short answer: there is no stock outbound field that carries ctx.requesterSessionKey through to your OpenAI-compatible backend.

What OpenClaw exposes on provider requests is, at most:

  • native OpenAI/Azure/Codex Responses transport only
    • headers:
      • x-client-request-id: <sessionId>
      • x-openclaw-session-id: <sessionId>
      • x-openclaw-turn-id: <turnId>
      • x-openclaw-turn-attempt: <attempt>
    • body:
      • metadata.openclaw_session_id
      • metadata.openclaw_turn_id
      • metadata.openclaw_turn_attempt

Important catch:

#
  • that is sessionId, not requesterSessionKey
  • and for a generic OpenAI-compatible base URL, stock OpenClaw does not attach those OpenClaw session headers/metadata at all

So for your exact setup, the receiving endpoint should assume:

  • Header carrying requesterSessionKey? no
  • Body field carrying requesterSessionKey? no
  • Anything equivalent in stock generic OpenAI-compatible mode? effectively no

If you need correlation back to the same OpenClaw session, you need a custom layer, usually one of these:

  1. provider plugin wrapStreamFn / payload hook to inject your own header/body field
  2. session-side binding store keyed by requesterSessionKey, then send only your own opaque token downstream
  3. custom OpenAI-compatible proxy that OpenClaw talks to, which adds your session correlation fields

So my recommendation is: don’t depend on stock provider requests to carry requesterSessionKey. Add your own explicit correlation field.

woven cave
#

api: openai-completions + WhatsApp (Baileys) — is there a config option or channel-plugin flag that populates body.user or a header with senderE164 on outbound provider requests? Or is resolveTransportTurnState only wired for the responses/Azure transports and not generic completions?

worthy mirageBOT
#

Let me verify the last bit so I don’t overstate it: whether there’s any hidden config path that maps sender info into user or provider headers for the completions transport.

#

Yeah, for api: openai-completions the answer is basically:

  • No, there is no stock config or WhatsApp channel flag that auto-populates body.user with senderE164
  • No, there is no stock per-message header injection of senderE164 on outbound provider calls
  • Yes, your reading is right: resolveTransportTurnState is wired into the Responses / Azure / WS-native transport path, not the generic chat-completions path

More concretely:

  • buildOpenAICompletionsParams(...) builds model, messages, stream, tools, etc. but does not set user
  • createOpenAICompletionsClient(...) uses configured provider headers, but there’s no built-in dynamic senderE164 mapping
  • models.providers.*.headers exists, but that is for static headers, not “inject this WhatsApp sender phone number for each request”

So for Baileys WhatsApp + openai-completions, stock OpenClaw does not forward senderE164 to your provider endpoint in either:

  • request body user
  • dynamic custom headers