openclaw - ✅(Solved) Fix [Bug]: BlueBubbles setup reuses the server password as the public webhook secret [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#70755Fetched 2026-04-24 05:54:00
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

Error Message

log.warn(plugin http route blocked without gateway auth (${pathContext.canonicalPath}));

Root Cause

Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path.

Fix Action

Fix / Workaround

// src/gateway/server/plugins-http.ts const requiresGatewayAuth = matchedPluginRoutesRequireGatewayAuth(matchedRoutes); if (requiresGatewayAuth && dispatchContext?.gatewayAuthSatisfied !== true) { log.warn(plugin http route blocked without gateway auth (${pathContext.canonicalPath})); return false; }

PR fix notes

PR #70757: fix: BlueBubbles setup reuses the server password as the public...

Description (problem / solution / changelog)

Summary

  • Problem: Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path. A remote caller who replays the token can forge webhook payloads, spoof senderId / handle.address, and drive the normal DM/group allowlist, pairing, and command-gating flow as that claimed sender. For deployments that rely on allowFrom, pairing, or owner-only command authorization to restrict who can steer the agent, this breaks the BlueBubbles channel auth boundary.
  • Why it matters: The follow-up keeps runtime and validation behavior aligned, preserves the dedicated webhook-secret requirement, and adds regression coverage for the inherited-credential path that previously blocked submission.
  • What changed: The follow-up keeps runtime and validation behavior aligned, preserves the dedicated webhook-secret requirement, and adds regression coverage for the inherited-credential path that previously blocked submission.
  • What did NOT change (scope boundary): No unrelated defaults, migrations, or compatibility behavior were intentionally changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #70755
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: The follow-up keeps runtime and validation behavior aligned, preserves the dedicated webhook-secret requirement, and adds regression coverage for the inherited-credential path that previously blocked submission.
  • Missing detection / guardrail: No narrower detection note was recorded in the issue bundle.
  • Contributing context (if known): See the linked issue analysis and changed files below.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this: Validation command pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test
  • Target test or file: Not explicitly recorded in the issue bundle.
  • Scenario the test should lock in: Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path. A remote caller who replays the token can forge webhook payloads, spoof senderId / handle.address, and drive the normal DM/group allowlist, pairing, and command-gating flow as that claimed sender. For deployments that rely on allowFrom, pairing, or owner-only command authorization to restrict who can steer the agent, this breaks the BlueBubbles channel auth boundary.
  • Why this is the smallest reliable guardrail: It validates the failing behavior on the touched path without expanding scope.
  • Existing test that already covers this (if any): Unknown.
  • If no new test is added, why not: The staged bundle did not record a narrower regression target.

User-visible / Behavior Changes

  • None beyond resolving the linked issue's broken behavior.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): Yes
  • New/changed network calls? (Yes/No): Yes
  • Command/tool execution surface changed? (Yes/No): Yes
  • Data access scope changed? (Yes/No): Yes
  • If any Yes, explain risk + mitigation: The follow-up keeps runtime and validation behavior aligned, preserves the dedicated webhook-secret requirement, and adds regression coverage for the inherited-credential path that previously blocked submission.. Mitigation: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test reported passed.

Repro + Verification

Environment

  • OS: N/A
  • Runtime/container: N/A
  • Model/provider: github-copilot/gpt-5.4
  • Integration/channel (if any): N/A
  • Relevant config (redacted): AI-assisted=yes

Steps

  1. Reproduce the linked issue using the recorded issue bundle.
  2. Apply the fix from this branch.
  3. Run pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test.

Expected

  • Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path. A remote caller who replays the token can forge webhook payloads, spoof senderId / handle.address, and drive the normal DM/group allowlist, pairing, and command-gating flow as that claimed sender. For deployments that rely on allowFrom, pairing, or owner-only command authorization to restrict who can steer the agent, this breaks the BlueBubbles channel auth boundary.
  • Validation passes for the touched behavior.

Actual

  • Validation status: passed

Evidence

  • Validation evidence: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test reported passed.
  • CVSS v3.1: 9.4 (Critical)
  • CVSS v4.0: 9.3 (Critical)
  • Changed files:
  • docs/.generated/config-baseline.sha256 (+2/-2)
  • docs/channels/bluebubbles.md (+12/-8)
  • docs/install/macos-vm.md (+2/-1)
  • docs/reference/secretref-credential-surface.md (+2/-0)
  • docs/reference/secretref-user-supplied-credentials-matrix.json (+14/-0)
  • extensions/bluebubbles/README.md (+1/-1)
  • extensions/bluebubbles/src/accounts.ts (+11/-3)
  • extensions/bluebubbles/src/channel-shared.ts (+1/-1)
  • extensions/bluebubbles/src/config-apply.ts (+4/-0)
  • extensions/bluebubbles/src/config-schema.ts (+101/-41)
  • extensions/bluebubbles/src/monitor-normalize.test.ts (+35/-0)
  • extensions/bluebubbles/src/monitor.test.ts (+12/-4)
  • extensions/bluebubbles/src/monitor.ts (+6/-1)
  • extensions/bluebubbles/src/monitor.webhook-auth.test.ts (+44/-58)
  • extensions/bluebubbles/src/monitor.webhook.test-helpers.ts (+7/-5)
  • extensions/bluebubbles/src/secret-contract.ts (+32/-0)
  • extensions/bluebubbles/src/secret-input.ts (+23/-0)
  • extensions/bluebubbles/src/setup-core.ts (+10/-2)
  • extensions/bluebubbles/src/setup-surface.test.ts (+247/-3)
  • extensions/bluebubbles/src/setup-surface.ts (+52/-7)
  • extensions/bluebubbles/src/status-issues.ts (+2/-2)
  • extensions/bluebubbles/src/types.ts (+2/-0)
  • src/channels/plugins/types.core.ts (+1/-0)
  • src/config/bundled-channel-config-metadata.generated.ts (+132/-0)
  • src/config/config.bluebubbles-webhook-secret-validation.test.ts (+115/-0)
  • src/config/io.write-prepare.test.ts (+2/-0)
  • src/config/plugin-auto-enable.test-helpers.ts (+1/-1)
  • src/config/zod-schema.providers-core.ts (+3/-0)
  • src/config/zod-schema.secret-input-validation.ts (+95/-1)

Human Verification (required)

  • Verified scenarios: Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path. A remote caller who replays the token can forge webhook payloads, spoof senderId / handle.address, and drive the normal DM/group allowlist, pairing, and command-gating flow as that claimed sender. For deployments that rely on allowFrom, pairing, or owner-only command authorization to restrict who can steer the agent, this breaks the BlueBubbles channel auth boundary.
  • Edge cases checked: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test completed with status passed.
  • What you did not verify: Interactive/manual scenarios not captured in the staged issue bundle.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Regression in the touched behavior while closing the linked issue.
    • Mitigation: pnpm exec oxlint src/ && pnpm build && pnpm check && pnpm test reported passed and the changed-file scope stayed limited to the recorded diff.

Changed files

  • docs/.generated/config-baseline.sha256 (modified, +2/-2)
  • docs/channels/bluebubbles.md (modified, +12/-8)
  • docs/install/macos-vm.md (modified, +2/-1)
  • docs/reference/secretref-credential-surface.md (modified, +2/-0)
  • docs/reference/secretref-user-supplied-credentials-matrix.json (modified, +14/-0)
  • extensions/bluebubbles/README.md (modified, +1/-1)
  • extensions/bluebubbles/package.json (modified, +4/-0)
  • extensions/bluebubbles/src/accounts.ts (modified, +11/-3)
  • extensions/bluebubbles/src/channel-shared.ts (modified, +1/-1)
  • extensions/bluebubbles/src/config-apply.ts (modified, +4/-0)
  • extensions/bluebubbles/src/config-schema.ts (modified, +101/-41)
  • extensions/bluebubbles/src/monitor-normalize.test.ts (modified, +35/-0)
  • extensions/bluebubbles/src/monitor.test.ts (modified, +12/-4)
  • extensions/bluebubbles/src/monitor.ts (modified, +6/-1)
  • extensions/bluebubbles/src/monitor.webhook-auth.test.ts (modified, +44/-58)
  • extensions/bluebubbles/src/monitor.webhook.test-helpers.ts (modified, +7/-5)
  • extensions/bluebubbles/src/secret-contract.ts (modified, +32/-0)
  • extensions/bluebubbles/src/secret-input.ts (modified, +2/-0)
  • extensions/bluebubbles/src/setup-core.ts (modified, +13/-2)
  • extensions/bluebubbles/src/setup-surface.test.ts (modified, +290/-3)
  • extensions/bluebubbles/src/setup-surface.ts (modified, +52/-7)
  • extensions/bluebubbles/src/status-issues.ts (modified, +2/-2)
  • extensions/bluebubbles/src/types.ts (modified, +2/-0)
  • src/channels/plugins/types.core.ts (modified, +1/-0)
  • src/config/bundled-channel-config-metadata.generated.ts (modified, +132/-0)
  • src/config/config.bluebubbles-webhook-secret-validation.test.ts (added, +141/-0)
  • src/config/io.write-prepare.test.ts (modified, +2/-0)
  • src/config/plugin-auto-enable.test-helpers.ts (modified, +1/-1)
  • src/config/zod-schema.providers-core.ts (modified, +3/-0)
  • src/config/zod-schema.secret-input-validation.ts (modified, +72/-1)
  • src/plugin-sdk/secret-input.test.ts (modified, +10/-0)
  • src/plugin-sdk/secret-input.ts (modified, +20/-0)
  • src/plugins/bundled-plugin-metadata.test.ts (modified, +14/-0)

Code Example

// extensions/bluebubbles/src/monitor.ts
const registered = registerWebhookTargetWithPluginRoute({
  targetsByPath: webhookTargets,
  target,
  route: {
    auth: "plugin",
    match: "exact",
    pluginId: "bluebubbles",
    source: "bluebubbles-webhook",
    accountId: target.account.accountId,
    log: target.runtime.log,
    handler: async (req, res) => {
      const handled = await handleBlueBubblesWebhookRequest(req, res);
      if (!handled && !res.headersSent) {
        res.statusCode = 404;
        res.setHeader("Content-Type", "text/plain; charset=utf-8");
        res.end("Not Found");
      }
    },
  },
});

const guidParam = url.searchParams.get("guid") ?? url.searchParams.get("password");
const headerToken =
  req.headers["x-guid"] ??
  req.headers["x-password"] ??
  req.headers["x-bluebubbles-guid"] ??
  req.headers["authorization"];
const guid = (Array.isArray(headerToken) ? headerToken[0] : headerToken) ?? guidParam ?? "";
const target = resolveWebhookTargetWithAuthOrRejectSync({
  targets,
  res,
  isMatch: (target) => {
    const token = target.account.config.password?.trim() ?? "";
    return safeEqualAuthToken(guid, token);
  },
});

// src/gateway/server/plugins-http.ts
const requiresGatewayAuth = matchedPluginRoutesRequireGatewayAuth(matchedRoutes);
if (requiresGatewayAuth && dispatchContext?.gatewayAuthSatisfied !== true) {
  log.warn(`plugin http route blocked without gateway auth (${pathContext.canonicalPath})`);
  return false;
}

for (const route of matchedRoutes) {
  if (route.auth !== "gateway") {
    continue;
  }
  // gateway auth enforcement only happens for auth:"gateway" routes
}

// extensions/bluebubbles/src/account-resolve.ts
const password =
  normalizeResolvedSecretInputString({
    value: params.password,
    path: "channels.bluebubbles.password",
  }) ||
  normalizeResolvedSecretInputString({
    value: account.config.password,
    path: `channels.bluebubbles.accounts.${account.accountId}.password`,
  });

// extensions/bluebubbles/src/client.ts
export function blueBubblesQueryStringAuth(password: string): BlueBubblesAuthStrategy {
  return {
    id: "query-string",
    decorate({ url }) {
      url.searchParams.set("password", password);
    },
  };
}

const authFactory = opts.authStrategy ?? blueBubblesQueryStringAuth;

// extensions/bluebubbles/src/monitor-normalize.ts
const senderIdRaw =
  readString(handle, "address") ??
  readString(handle, "handle") ??
  readString(handle, "id") ??
  readString(message, "senderId") ??
  readString(message, "sender") ??
  readString(message, "from") ??
  "";

const normalizedSender = normalizeBlueBubblesHandle(senderId || senderFallbackFromChatGuid || "");
return {
  text,
  senderId: normalizedSender,
  senderIdExplicit,
  senderName,
  // ...
};

// docs/channels/bluebubbles.md
4. Point BlueBubbles webhooks to your gateway
   (example: https://your-gateway-host:3000/bluebubbles-webhook?password=<password>)

- Webhook authentication is always required. OpenClaw rejects BlueBubbles webhook requests
  unless they include a password/guid that matches `channels.bluebubbles.password`

---

curl -X POST "https://gateway-host:3000/bluebubbles-webhook?password=secret-token" \
     -H "Content-Type: application/json" \
     --data '{"type":"new-message","data":{"text":"run /status","handle":{"address":"+15551234567"},"isGroup":false,"isFromMe":false,"guid":"msg-1"}}'
RAW_BUFFERClick to expand / collapse

Severity Assessment

CVSS Assessment

Metricv3.1v4.0
Score9.4 / 10.09.3 / 10.0
SeverityCriticalCritical
VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:LCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N
CalculatorCVSS v3.1 CalculatorCVSS v4.0 Calculator

Threat Model Alignment

Classification: security-specific

SECURITY.md says webhook-driven payloads must be treated as untrusted content and that auth boundaries are part of OpenClaw's security boundary. BlueBubbles registers /bluebubbles-webhook as a plugin-auth route rather than a gateway-auth route, so the comparison against channels.bluebubbles.password is the operative network authentication check for this surface. This is not the out-of-scope case of exposing a third-party secret with no OpenClaw impact: OpenClaw itself consumes the leaked value to authorize inbound BlueBubbles webhook traffic and then trusts that authenticated payload to drive sender authorization and agent delivery.

Impact

Anyone who sees the public BlueBubbles webhook URL, or any URL telemetry that captured it, learns a reusable credential that OpenClaw itself accepts on the BlueBubbles webhook route. Because that same value is also the configured BlueBubbles server/API password, the leak couples passive credential exposure with a live auth bypass on the inbound BlueBubbles path.

A remote caller who replays the token can forge webhook payloads, spoof senderId / handle.address, and drive the normal DM/group allowlist, pairing, and command-gating flow as that claimed sender. For deployments that rely on allowFrom, pairing, or owner-only command authorization to restrict who can steer the agent, this breaks the BlueBubbles channel auth boundary.

Affected Component

Files: extensions/bluebubbles/src/monitor.ts, extensions/bluebubbles/src/monitor-normalize.ts, extensions/bluebubbles/src/monitor-processing.ts, extensions/bluebubbles/src/account-resolve.ts, extensions/bluebubbles/src/client.ts, extensions/bluebubbles/src/client.test.ts, extensions/bluebubbles/src/config-schema.ts, extensions/bluebubbles/src/types.ts, extensions/bluebubbles/src/secret-contract.ts, extensions/bluebubbles/src/setup-core.ts, extensions/bluebubbles/src/setup-surface.ts, extensions/bluebubbles/src/monitor.webhook-auth.test.ts, src/gateway/server/plugins-http.ts, docs/channels/bluebubbles.md, docs/install/macos-vm.md, docs/reference/secretref-credential-surface.md

// extensions/bluebubbles/src/monitor.ts
const registered = registerWebhookTargetWithPluginRoute({
  targetsByPath: webhookTargets,
  target,
  route: {
    auth: "plugin",
    match: "exact",
    pluginId: "bluebubbles",
    source: "bluebubbles-webhook",
    accountId: target.account.accountId,
    log: target.runtime.log,
    handler: async (req, res) => {
      const handled = await handleBlueBubblesWebhookRequest(req, res);
      if (!handled && !res.headersSent) {
        res.statusCode = 404;
        res.setHeader("Content-Type", "text/plain; charset=utf-8");
        res.end("Not Found");
      }
    },
  },
});

const guidParam = url.searchParams.get("guid") ?? url.searchParams.get("password");
const headerToken =
  req.headers["x-guid"] ??
  req.headers["x-password"] ??
  req.headers["x-bluebubbles-guid"] ??
  req.headers["authorization"];
const guid = (Array.isArray(headerToken) ? headerToken[0] : headerToken) ?? guidParam ?? "";
const target = resolveWebhookTargetWithAuthOrRejectSync({
  targets,
  res,
  isMatch: (target) => {
    const token = target.account.config.password?.trim() ?? "";
    return safeEqualAuthToken(guid, token);
  },
});

// src/gateway/server/plugins-http.ts
const requiresGatewayAuth = matchedPluginRoutesRequireGatewayAuth(matchedRoutes);
if (requiresGatewayAuth && dispatchContext?.gatewayAuthSatisfied !== true) {
  log.warn(`plugin http route blocked without gateway auth (${pathContext.canonicalPath})`);
  return false;
}

for (const route of matchedRoutes) {
  if (route.auth !== "gateway") {
    continue;
  }
  // gateway auth enforcement only happens for auth:"gateway" routes
}

// extensions/bluebubbles/src/account-resolve.ts
const password =
  normalizeResolvedSecretInputString({
    value: params.password,
    path: "channels.bluebubbles.password",
  }) ||
  normalizeResolvedSecretInputString({
    value: account.config.password,
    path: `channels.bluebubbles.accounts.${account.accountId}.password`,
  });

// extensions/bluebubbles/src/client.ts
export function blueBubblesQueryStringAuth(password: string): BlueBubblesAuthStrategy {
  return {
    id: "query-string",
    decorate({ url }) {
      url.searchParams.set("password", password);
    },
  };
}

const authFactory = opts.authStrategy ?? blueBubblesQueryStringAuth;

// extensions/bluebubbles/src/monitor-normalize.ts
const senderIdRaw =
  readString(handle, "address") ??
  readString(handle, "handle") ??
  readString(handle, "id") ??
  readString(message, "senderId") ??
  readString(message, "sender") ??
  readString(message, "from") ??
  "";

const normalizedSender = normalizeBlueBubblesHandle(senderId || senderFallbackFromChatGuid || "");
return {
  text,
  senderId: normalizedSender,
  senderIdExplicit,
  senderName,
  // ...
};

// docs/channels/bluebubbles.md
4. Point BlueBubbles webhooks to your gateway
   (example: https://your-gateway-host:3000/bluebubbles-webhook?password=<password>)

- Webhook authentication is always required. OpenClaw rejects BlueBubbles webhook requests
  unless they include a password/guid that matches `channels.bluebubbles.password`

Technical Reproduction

  1. Configure BlueBubbles with channels.bluebubbles.password="secret-token", dmPolicy="allowlist", and allowFrom=["+15551234567"].
  2. Follow the shipped BlueBubbles docs. Current public docs still tell operators to publish the webhook as https://gateway-host:3000/bluebubbles-webhook?password=secret-token in docs/channels/bluebubbles.md and docs/install/macos-vm.md.
  3. Recover that URL from any place that records or copies request URLs, such as reverse-proxy access logs, copied webhook configuration, browser history, or operator console transcripts.
  4. From any remote host, send a forged webhook using the leaked password and an allowlisted sender id:
    curl -X POST "https://gateway-host:3000/bluebubbles-webhook?password=secret-token" \
      -H "Content-Type: application/json" \
      --data '{"type":"new-message","data":{"text":"run /status","handle":{"address":"+15551234567"},"isGroup":false,"isFromMe":false,"guid":"msg-1"}}'
  5. handleBlueBubblesWebhookRequest() authenticates the request only by comparing the caller-supplied query/header token to target.account.config.password. The BlueBubbles route is plugin-authenticated, not gateway-authenticated, so that password check is the route's effective network auth boundary.
  6. normalizeWebhookMessage() trusts handle.address / senderId from the caller-controlled JSON payload, and monitor-processing.ts uses that unverified sender id for DM/group allowlist checks, command authorization, routing, pairing state, and the agent context payload.
  7. The plugin routes the forged message to the agent as if it came from the real BlueBubbles contact.

Demonstrated Impact

SECURITY.md says webhook-driven payloads should be treated as untrusted content and that security boundaries come from auth, tool policy, sandboxing, and approvals. This issue breaks that auth boundary because OpenClaw uses channels.bluebubbles.password both as the BlueBubbles server/API credential and as the inbound BlueBubbles webhook secret.

This is not the out-of-scope case of exposing a third-party or user-controlled secret with no OpenClaw impact. The leaked value is the exact credential OpenClaw relies on to authorize the BlueBubbles webhook route, and the authenticated payload controls senderId, chatGuid, and chatId values that feed the channel's allowlist, pairing, and command authorization logic. Once the URL leaks, an unauthorized network caller can impersonate already-allowed BlueBubbles senders and drive the agent through the normal inbound message path.

Environment

Verified against release v2026.4.22 (published 2026-04-23T13:56:23Z) at commit main. Preconditions: BlueBubbles enabled with a configured channels.bluebubbles.password, plus any deployment path that records or shares the public webhook URL.

Remediation Advice

Add a dedicated BlueBubbles webhook credential surface and use that separate secret for inbound webhook authentication. Keep channels.bluebubbles.password reserved for BlueBubbles server/API access.

Update runtime, schema, setup flows, secret registration, tests, and docs so operators no longer publish or forward the BlueBubbles server password on the public webhook path. Header-based transport is a useful hardening step where BlueBubbles supports it, but it is not a substitute for separating the inbound webhook secret from the outbound API password.

A good fix should:

  • add a distinct webhookSecret or equivalent config field
  • authenticate webhook requests only against that field
  • stop documenting ?password=<password> as the public webhook setup
  • preserve header-based auth as an optional transport hardening step, not the primary fix
  • update config/runtime/setup/docs/tests consistently so BlueBubbles matches the repo's existing webhookSecret pattern used by other webhook-based channels
<!-- submission-marker:DE-vnw-bluebubbles-password-in-urls-leaks-reusable-webhook-secret -->

extent analysis

TL;DR

Separate the BlueBubbles webhook secret from the server/API password by introducing a dedicated webhookSecret config field and updating the authentication logic to use this new secret.

Guidance

  • Introduce a new config field, e.g., webhookSecret, specifically for authenticating inbound BlueBubbles webhooks.
  • Update the handleBlueBubblesWebhookRequest function to authenticate requests against the new webhookSecret instead of channels.bluebubbles.password.
  • Remove documentation that suggests publishing the BlueBubbles server password in the public webhook URL.
  • Preserve header-based authentication as an optional hardening step.
  • Ensure consistent updates across config, runtime, setup, documentation, and tests to reflect the new webhookSecret pattern.

Example

// extensions/bluebubbles/src/monitor.ts
const target = resolveWebhookTargetWithAuthOrRejectSync({
  targets,
  res,
  isMatch: (target) => {
    const token = target.account.config.webhookSecret?.trim() ?? "";
    return safeEqualAuthToken(guid, token);
  },
});

Notes

This fix assumes that the webhookSecret field will be stored and managed securely, similar to other sensitive credentials in the system.

Recommendation

Apply the workaround by introducing a separate webhookSecret config field and updating the authentication logic to prevent the leakage of reusable credentials. This change will help mitigate the security risk associated with exposing the BlueBubbles server password in the public webhook URL.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: BlueBubbles setup reuses the server password as the public webhook secret [1 pull requests, 1 participants]