TL;DR
After connecting OpenClaw to a self-hosted Mattermost instance, agents completely ignored images sent in chat. The culprit: OpenClaw's built-in SSRF (Server-Side Request Forgery) protection was automatically blocking all fetches to private 192.168.x.x addresses. The fix was a single line of code — but tracking down the cause took a while.
Background
My home lab runs Mattermost on a self-hosted server. The OpenClaw Mattermost plugin connected and text chat worked perfectly. But the moment a user sent an image, the agent acted like it didn't exist.
Manual curl fetched the image just fine. Agent config looked correct. So what was happening?
Investigation
Grepping the Gateway logs revealed the cause immediately:
blocked URL fetch target=http://192.168.x.x:8065/api/v4/files/xxxxx
reason=Blocked hostname or private/internal/special-use IP address
OpenClaw's fetchRemoteMedia function has built-in SSRF protection. This is entirely correct security design — since agents can fetch arbitrary URLs, internal network access should be blocked by default.
The problem: a self-hosted Mattermost server lives exactly on that "internal network".
Root Cause
When monitor.ts in the Mattermost plugin fetches images, it was not passing the ssrfPolicy option. The default SSRF policy blocks all RFC1918 addresses (10.x.x.x, 172.16-31.x.x, 192.168.x.x).
Interestingly, the audio transcription code already had logic to automatically set allowPrivateNetwork when baseUrl is an internal IP. That consideration simply hadn't been applied to the media fetch side.
The Fix
Add one option to the fetchRemoteMedia call in monitor.ts:
// Before
const media = await fetchRemoteMedia(url);
// After
const media = await fetchRemoteMedia(url, {
ssrfPolicy: { allowPrivateNetwork: true }
});
The SsrfPolicy type supports these options:
-
allowPrivateNetwork: Permit RFC1918 addresses -
dangerouslyAllowPrivateNetwork: Broader permission (includes loopback) -
allowedHostnames/hostnameAllowlist: Hostname-based allowlisting
For self-hosted environments, allowPrivateNetwork: true is sufficient.
Deployment and Caveats
Applied the patch across all nodes and restarted all gateways. Image recognition started working immediately.
One important caveat: this patch modifies an OpenClaw system file (extensions/mattermost/src/mattermost/monitor.ts) directly. That means an OpenClaw update will overwrite it.
Mitigations:
- Prepare a patch script and auto-apply after updates
- Submit a PR to OpenClaw proposing auto-detection for self-hosted environments
- Allow
ssrfPolicyto be specified in config files
Option 2 is the right long-term approach. Automatically setting allowPrivateNetwork when baseUrl is an internal IP is exactly what the audio transcription code already does — it just needs to be applied to media fetch as well.
Lessons Learned
- SSRF protection ON by default is correct. Getting tripped up in a self-hosted environment is the flip side of good design.
- Self-hosted = internal network is easy to forget. This problem simply doesn't exist with cloud SaaS.
-
Read the logs. The
blocked URL fetchmessage was unambiguous — but without reading logs, the symptom was just "images don't work." - Check other features in the same codebase. The pattern was already solved on the audio side; it just wasn't applied to media.
Appendix: Image Recognition Config
After solving the SSRF issue, one more step was needed: configuring tools.media.image. Even if OpenClaw can now fetch images from Mattermost, a Vision model is required to actually understand them:
{
"tools": {
"media": {
"image": {
"models": [{
"provider": "anthropic",
"model": "claude-sonnet-4-20250514"
}]
}
}
}
}
With this in place, images sent to agents are pre-analyzed by Sonnet and the description is passed to the agent.
Environment: Ubuntu 24.04 / OpenClaw 2026.3.x / Mattermost self-hosted / Home cluster
Top comments (0)