Skip to content

fix(security): default standalone servers to loopback bind#12370

Closed
davidrudduck wants to merge 20 commits intoopenclaw:mainfrom
davidrudduck:fix/default-bind-loopback
Closed

fix(security): default standalone servers to loopback bind#12370
davidrudduck wants to merge 20 commits intoopenclaw:mainfrom
davidrudduck:fix/default-bind-loopback

Conversation

@davidrudduck
Copy link
Contributor

@davidrudduck davidrudduck commented Feb 9, 2026

Summary

  • Change canvas host and telegram webhook default bind from 0.0.0.0 (all interfaces) to 127.0.0.1 (loopback only)
  • Prevents unintended network exposure when no explicit host is configured

Key Changes

  • src/canvas-host/server.ts: Default listenHost fallback "0.0.0.0""127.0.0.1"
  • src/telegram/webhook.ts: Default host fallback "0.0.0.0""127.0.0.1"

How It Works

  • No user action required for local/Tailscale deployments — services remain accessible on localhost
  • Users who need LAN/external access can still set listenHost / host explicitly in their configuration
  • The gateway server already has a proper bind resolution system (auto/loopback/lan/custom) — these standalone servers were bypassing it

Files Changed

File Change
src/canvas-host/server.ts:452 Default bind "0.0.0.0""127.0.0.1"
src/telegram/webhook.ts:36 Default bind "0.0.0.0""127.0.0.1"

Testing

  • Type check (pnpm tsgo): passes clean
  • Linter: passes clean
  • CodeRabbit review: zero comments (rated "Trivial")

Greptile Overview

Greptile Summary

This PR tightens default network exposure by switching the standalone Canvas host and Telegram webhook bind defaults from 0.0.0.0 to 127.0.0.1.

Along the way, it also hardens several .trim() call sites against undefined inputs, updates workspace resolution in the embedded runner to use resolveRunWorkspaceDir, and introduces a new message_sent hook path in both the reply dispatcher and outbound delivery pipeline (with accompanying tests).

Confidence Score: 4/5

  • Generally safe to merge, but the new message_sent hook reporting is incorrect under bestEffort delivery.
  • Main security-focused bind default change is straightforward, but the added hook in deliverOutboundPayloads contradicts its own comment and can emit message_sent content for payloads that were not actually delivered when bestEffort is enabled.
  • src/infra/outbound/deliver.ts

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

davidrudduck and others added 20 commits February 8, 2026 23:02
The actual crash site: contextFiles array can contain entries with
undefined path. buildAgentSystemPrompt calls file.path.trim() inside
Array.some() to detect SOUL.md — crashes when path is undefined.

Stack trace:
  at buildAgentSystemPrompt (system-prompt.ts)
  at buildEmbeddedSystemPrompt
  at runEmbeddedAttempt

This was the real .trim() crash killing all sub-agent spawns at ~34ms.

Refs: openclaw/openclaw#6535
…path

Add null coalescing (?? '') before .trim() in:
- src/gateway/server-methods/agent.ts (request.message, p.runId)
- src/gateway/session-utils.ts (params.sessionKey)
- src/agents/subagent-registry.ts (requesterSessionKey)
- src/agents/subagent-announce.ts (requesterSessionKey)

Fixes TypeError: Cannot read properties of undefined (reading 'trim')
that crashes subagent spawns at ~34ms before the agent session starts.

Refs: openclaw/openclaw#6535
runMessageSent was defined in hooks.ts but never called anywhere.
This wires it into deliverOutboundPayloads() in deliver.ts so plugins
listening via api.on('message_sent') actually receive events.

Fixes #6535, #5513, #4006
Discord uses deliverDiscordReply() which bypasses the generic
deliverOutboundPayloads(). Added hook call in reply-delivery.ts
so Discord outbound messages also fire message_sent.

Refs #6535
Move message_sent hook from channel-specific delivery paths to the
central createReplyDispatcher(). This fires for ALL channels
(Discord, WhatsApp, Signal, Slack, etc.) without patching each one.

Added optional hookContext to ReplyDispatcherOptions for callers to
provide channel/routing context. Falls back to empty strings when
not provided — plugin still gets the message content.

Removed Discord-specific hook from reply-delivery.ts (now redundant).
Kept deliver.ts hook for non-dispatcher delivery paths (cron, heartbeat).

Refs #6535
- 5 tests for reply-dispatcher: hook fires on delivery, passes hookContext,
  skips on failure/empty text, errors don't break delivery
- 2 tests added to deliver.test.ts: hook fires with combined text,
  errors don't propagate

All 19 tests pass. Covers both centralised (dispatcher) and
fallback (deliverOutboundPayloads) hook call sites.

Refs #6535
When bestEffort is enabled, failed payloads are skipped via onError.
Only include text from payloads that produced delivery results to
avoid reporting undelivered content as success.

Co-authored-by: CodeRabbit <support@coderabbit.ai>
Require hookContext.channelId to be present before firing the hook.
Callers must opt in with valid context — prevents emitting empty/invalid
fields that would break plugins expecting meaningful channel metadata.

Co-authored-by: CodeRabbit <support@coderabbit.ai>
- reply-dispatcher: use empty string fallback for `to` instead of channelId
  (channelId is a channel kind like "telegram", not a destination)
- deliver: remove broken .slice(0, results.length) — results count sends
  not payloads, so chunking and bestEffort failures misreport text
The actual crash site: contextFiles array can contain entries with
undefined path. buildAgentSystemPrompt calls file.path.trim() inside
Array.some() to detect SOUL.md — crashes when path is undefined.

Stack trace:
  at buildAgentSystemPrompt (system-prompt.ts)
  at buildEmbeddedSystemPrompt
  at runEmbeddedAttempt

This was the real .trim() crash killing all sub-agent spawns at ~34ms.

Refs: openclaw/openclaw#6535
…path

Add null coalescing (?? '') before .trim() in:
- src/gateway/server-methods/agent.ts (request.message, p.runId)
- src/gateway/session-utils.ts (params.sessionKey)
- src/agents/subagent-registry.ts (requesterSessionKey)
- src/agents/subagent-announce.ts (requesterSessionKey)

Fixes TypeError: Cannot read properties of undefined (reading 'trim')
that crashes subagent spawns at ~34ms before the agent session starts.

Refs: openclaw/openclaw#6535
Remove 23 tracked .omc/ files (session data, project memory, checkpoints)
that should never have been committed. These are local Claude Code tooling
artefacts.
Replace `"undefined"` literal with `"(unknown)"` to match the empty-string
guard used in path normalization and avoid leaking TS artifacts into model context.
# Conflicts:
#	src/agents/system-prompt.ts
…nsistency

When sandbox is enabled with read-only workspace access, effectiveWorkspace
differs from resolvedWorkspace. All other call sites already use
effectiveWorkspace — createAgentSession was the only outlier, causing the
agent session cwd to point at a directory the sandbox may not allow access to.
Change canvas host and telegram webhook default bind from 0.0.0.0
(all interfaces) to 127.0.0.1 (loopback only) to prevent unintended
network exposure when no explicit host is configured.
@openclaw-barnacle openclaw-barnacle bot added channel: telegram Channel integration: telegram gateway Gateway runtime agents Agent runtime and tooling labels Feb 9, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +379 to +384
const hookRunner = getGlobalHookRunner();
if (hookRunner && results.length > 0) {
const deliveredText = normalizedPayloads
.map((p) => p.text ?? "")
.filter(Boolean)
.join("\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect delivered text

deliverOutboundPayloads says it will “only report text from payloads that produced delivery results”, but deliveredText is built from all normalizedPayloads (normalizedPayloads.map(...)) as long as results.length > 0. With bestEffort: true, if an early payload fails and a later one succeeds, results.length is still >0 and the hook will incorrectly include text from the failed payload(s), reporting content that was never delivered.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/outbound/deliver.ts
Line: 379:384

Comment:
**Incorrect delivered text**

`deliverOutboundPayloads` says it will “only report text from payloads that produced delivery results”, but `deliveredText` is built from *all* `normalizedPayloads` (`normalizedPayloads.map(...)`) as long as `results.length > 0`. With `bestEffort: true`, if an early payload fails and a later one succeeds, `results.length` is still >0 and the hook will incorrectly include text from the failed payload(s), reporting content that was never delivered.

How can I resolve this? If you propose a fix, please make it concise.

@davidrudduck davidrudduck deleted the fix/default-bind-loopback branch February 9, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: telegram Channel integration: telegram gateway Gateway runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant