Skip to content

fix: BlueBubbles webhook auth bypass via loopback proxy trust#13787

Merged
Takhoffman merged 3 commits intoopenclaw:mainfrom
coygeek:codex/an-08
Feb 12, 2026
Merged

fix: BlueBubbles webhook auth bypass via loopback proxy trust#13787
Takhoffman merged 3 commits intoopenclaw:mainfrom
coygeek:codex/an-08

Conversation

@coygeek
Copy link
Contributor

@coygeek coygeek commented Feb 11, 2026

Fix Summary

The BlueBubbles webhook handler bypasses its shared-secret check whenever the incoming socket address is loopback. In common same-host reverse-proxy deployments, all attacker traffic arrives as 127.0.0.1 at the gateway, so external callers can inject webhook events without knowing the BlueBubbles password.

Issue Linkage

Fixes #13786

Security Snapshot

  • CVSS v3.1: 8.6 (High)
  • CVSS v4.0: 8.8 (High)

Implementation Details

Files Changed

  • extensions/bluebubbles/src/monitor.test.ts (+40/-28)
  • extensions/bluebubbles/src/monitor.ts (+0/-4)

Technical Analysis

BlueBubbles registers a plugin HTTP handler on the gateway plugin route path. During authentication, the handler accepts valid shared-secret tokens, but also unconditionally accepts requests from loopback remote addresses. Because reverse proxies commonly forward to 127.0.0.1:18789, this creates an alternate unauthenticated channel that bypasses webhook secret verification.

Validation Evidence

  • Command: pnpm build && pnpm check && pnpm test
  • Status: passed

Risk and Compatibility

non-breaking; no known regression impact

AI-Assisted Disclosure

  • AI-assisted: yes
  • Model: GPT-5.3-Codex

Greptile Overview

Greptile Summary

This PR removes a loopback-based authentication bypass in the BlueBubbles webhook handler (handleBlueBubblesWebhookRequest), so requests from 127.0.0.1/::1/IPv4-mapped loopback no longer skip the shared-secret check (important for same-host reverse-proxy deployments where upstream connects from loopback).

The test suite is updated to assert that loopback-origin requests still require a valid password when one is configured, and the request mock helper was modified to auto-add auth in many cases to keep unrelated tests passing.

Main concern: the new implicit-auth behavior in createMockRequest can unintentionally turn “unauthenticated” test requests into authenticated ones, reducing coverage for auth failures and making future regressions harder to catch.

Confidence Score: 4/5

  • This PR is largely safe to merge and addresses the reported auth bypass, with one test-helper change that could weaken auth-related test coverage.
  • The production change is small and directly removes the loopback trust shortcut in webhook auth. The main risk is in tests: createMockRequest now injects auth implicitly, which can mask missing-auth scenarios and allow regressions to slip through without failing tests.
  • extensions/bluebubbles/src/monitor.test.ts

Generated by staged fix workflow.
Generated by staged fix workflow.
Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
@openclaw-barnacle openclaw-barnacle bot added the channel: bluebubbles Channel integration: bluebubbles label Feb 11, 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.

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +257 to +270
const parsedUrl = new URL(url, "http://localhost");
const hasAuthQuery = parsedUrl.searchParams.has("guid") || parsedUrl.searchParams.has("password");
const hasAuthHeader =
headers["x-guid"] !== undefined ||
headers["x-password"] !== undefined ||
headers["x-bluebubbles-guid"] !== undefined ||
headers.authorization !== undefined;
if (!hasAuthQuery && !hasAuthHeader) {
parsedUrl.searchParams.set("password", "test-password");
}

const req = new EventEmitter() as IncomingMessage;
req.method = method;
req.url = url;
req.url = `${parsedUrl.pathname}${parsedUrl.search}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Auto-auth masks tests

createMockRequest now silently injects an auth query parameter when neither auth query params nor auth headers are present. Because the default mock account uses the same default password, a request created without any explicit auth can become implicitly authenticated, which can hide regressions in webhook auth handling. Prefer keeping createMockRequest faithful (no implicit auth) and making tests add auth explicitly when needed (or gate this behavior behind an explicit option so unauthenticated requests stay unauthenticated).

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/bluebubbles/src/monitor.test.ts
Line: 257:270

Comment:
**Auto-auth masks tests**

`createMockRequest` now silently injects an auth query parameter when neither auth query params nor auth headers are present. Because the default mock account uses the same default password, a request created without any explicit auth can become implicitly authenticated, which can hide regressions in webhook auth handling. Prefer keeping `createMockRequest` faithful (no implicit auth) and making tests add auth explicitly when needed (or gate this behavior behind an explicit option so unauthenticated requests stay unauthenticated).

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

Copy link
Contributor

@asklee-klawd asklee-klawd left a comment

Choose a reason for hiding this comment

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

Great catch on the loopback auth bypass! The core security fix is solid - removing the loopback trust shortcut closes a real vulnerability in reverse-proxy deployments.

Concern: Test Helper Auto-Auth

The createMockRequest modification auto-injects auth when none is present:

if (!hasAuthQuery && !hasAuthHeader) {
  parsedUrl.searchParams.set("password", "test-password");
}

This weakens test coverage for authentication failures. Future tests calling createMockRequest() without auth will now pass authenticated, potentially missing regressions.

Suggestion:

Consider one of these approaches:

  1. Explicit auth parameter: Add an optional auth param to createMockRequest():

    function createMockRequest(
      method: string,
      url: string,
      body: unknown,
      headers: Record<string, string> = {},
      auth: boolean | string = true  // explicit default
    )
  2. Separate helper: Create createAuthenticatedRequest() and keep createMockRequest() auth-agnostic.

  3. Opt-in flag: Add skipAuth: true parameter to explicitly opt out.

This preserves the fix while keeping tests explicit about auth intent. What do you think?

Minor: The new loopback test is excellent and covers all three address formats. Well done.

Overall: Approve with suggestion - the security fix is critical and correct; the test helper is worth revisiting but not blocking.

Copy link

@silence1ai silence1ai left a comment

Choose a reason for hiding this comment

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

Important security fix! Trusting loopback addresses for webhook auth bypass is a classic vulnerability — any process on the same machine could forge webhook requests without credentials.

What I liked:

  • The fix is minimal and surgical — just removing the loopback trust block
  • Tests updated comprehensively, covering 127.0.0.1, ::1, and ::ffff:127.0.0.1
  • The test helper createMockRequest now auto-injects auth when not explicitly testing auth scenarios — nice DX improvement

suggestion: The test still calls it "requires authentication for loopback requests when password is configured" — have you considered also adding a test for when no password is configured? Just to confirm the behavior when account.password is unset (should that be an open webhook or still require some auth?).

nit: In the updated createMockRequest, the default password injection (parsedUrl.searchParams.set("password", "test-password")) is clever, but the magic string "test-password" could be extracted to a constant shared with the account setup to avoid test fragility.

Great security improvement! 🔒

@Takhoffman Takhoffman merged commit f836c38 into openclaw:main Feb 12, 2026
20 of 21 checks passed
@ghost
Copy link

ghost commented Feb 12, 2026

This is not what I expected.. it’s not kosher. I or anyone else for that matter can have malicious intentions while “contributing” . I need feedback on this from anyone

ENCHIGO pushed a commit to ENCHIGO/openclaw that referenced this pull request Feb 12, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Feb 12, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.

(cherry picked from commit f836c38)
wsman pushed a commit to wsman/openclaw that referenced this pull request Feb 12, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
sauerdaniel pushed a commit to sauerdaniel/openclaw that referenced this pull request Feb 12, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
steipete pushed a commit to Diaspar4u/openclaw that referenced this pull request Feb 13, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
dbachelder pushed a commit to dbachelder/openclaw that referenced this pull request Feb 13, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
Baukebrenninkmeijer pushed a commit to orq-ai/openclaw that referenced this pull request Feb 13, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
skyhawk14 pushed a commit to skyhawk14/openclaw that referenced this pull request Feb 13, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
jbold added a commit to jbold/openclaw that referenced this pull request Feb 15, 2026
* chore: bump version to 2026.2.10

* docs: start 2026.2.10 changelog section

* Feat/litellm provider (#12823)

* feat: add LiteLLM provider types, env var, credentials, and auth choice

Add litellm-api-key auth choice, LITELLM_API_KEY env var mapping,
setLitellmApiKey() credential storage, and LITELLM_DEFAULT_MODEL_REF.

* feat: add LiteLLM onboarding handler and provider config

Add applyLitellmProviderConfig which properly registers
models.providers.litellm with baseUrl, api type, and model definitions.
This fixes the critical bug from PR #6488 where the provider entry was
never created, causing model resolution to fail at runtime.

* docs: add LiteLLM provider documentation

Add setup guide covering onboarding, manual config, virtual keys,
model routing, and usage tracking. Link from provider index.

* docs: add LiteLLM to sidebar navigation in docs.json

Add providers/litellm to both English and Chinese provider page lists
so the docs page appears in the sidebar navigation.

* test: add LiteLLM non-interactive onboarding test

Wire up litellmApiKey flag inference and auth-choice handler for the
non-interactive onboarding path, and add an integration test covering
profile, model default, and credential storage.

* fix: register --litellm-api-key CLI flag and add preferred provider mapping

Wire up the missing Commander CLI option, action handler mapping, and
help text for --litellm-api-key. Add litellm-api-key to the preferred
provider map for consistency with other providers.

* fix: remove zh-CN sidebar entry for litellm (no localized page yet)

* style: format buildLitellmModelDefinition return type

* fix(onboarding): harden LiteLLM provider setup (#12823)

* refactor(onboarding): keep auth-choice provider dispatcher under size limit

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(web-search): handle xAI Responses API format in Grok provider

The xAI /v1/responses API returns content in a structured format with
typed output blocks (type: 'message') containing typed content blocks
(type: 'output_text') and url_citation annotations. The previous code
only checked output[0].content[0].text without filtering by type,
which could miss content in responses with multiple output entries.

Changes:
- Update GrokSearchResponse type to include annotations on content blocks
- Filter output blocks by type='message' and content by type='output_text'
- Extract url_citation annotations as fallback citations when top-level
  citations array is empty
- Deduplicate annotation-derived citation URLs
- Update tests for the new structured return type

Closes #13520

* fix(gateway): default-deny missing connect scopes

* fix(plugins): ignore install scripts during plugin/hook install

* fix: don't lowercase Slack channel IDs (#14055)

* feat: Add --localTime option to logs command for local timezone display (#13818)

* feat: add --localTime options to make logs to show time with local time zone

fix #12447

* fix: prep logs local-time option and docs (#13818) (thanks @xialonglee)

---------

Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>

* fix(config): avoid redacting maxTokens-like fields (#14006)

* fix(config): avoid redacting maxTokens-like fields

* fix(config): finalize redaction prep items (#14006) (thanks @constansino)

---------

Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>

* fix(cli): drop logs --localTime alias noise

* chore(irc): sync plugin version to 2026.2.10

* chore: update AGENTS.md and add mintlify skill (#14123)

* docs(skills): update mintlify skill to reference docs/ directory (#14125)

* docs: modernize gateway configuration page (Phase 1) (#14111)

* docs(configuration): split into overview + full reference with Mintlify components

* docs(configuration): use tooltip for JSON5 format note

* docs(configuration): fix Accordion closing tags inside list contexts

* docs(configuration): expand intro to reflect full config surface

* docs(configuration): trim intro to three concise bullets

* docs(configuration-examples): revert all branch changes

* docs(configuration): improve hot-reload section with tabs and accordion

* docs(configuration): uncramp hot-reload — subheadings, bullet list, warning

* docs(configuration): restore hot-apply vs restart table

* docs(configuration): fix hot-reload table against codebase

* docs: add configuration-reference.md — full field-by-field reference

* docs(gateway): refresh runbook and align config reference

* docs: include pending docs updates and install graphic

* fix: use configured base URL for Ollama model discovery (#14131)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 2292d2de6d6a4608fba8124ace738df75ae890fc
Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix(slack): detect control commands when message starts with @mention (#14142)

Merged via /review-pr-v2 -> /prepare-pr-v2 -> /merge-pr-v2.

Prepared head SHA: cb0b4f6a3b675bceb7e59b8939b4800f813bc069
Co-authored-by: beefiker <55247450+beefiker@users.noreply.github.com>
Co-authored-by: gumadeiras <gumadeiras@gmail.com>
Reviewed-by: @gumadeiras

* docs(channels): modernize telegram docs page (#14168)

* docs(channels): fix telegram card icon (#14193)

* PI: assign landpr to self

* docs(channels): modernize discord docs page (#14190)

* docs(nav): move grammy page to technical reference (#14198)

* fix(discord): default standalone threads to public type (#14147)

When creating a Discord thread without a messageId, the API defaults
to type 12 (private thread) which is unexpected. This adds an explicit
type: PublicThread (11) for standalone thread creation in non-forum
channels, matching user expectations.

Closes #14147

* fix(discord): default standalone threads to public type (#14147)

When creating a Discord thread without a messageId (standalone thread),
the Discord API defaults to type 12 (private). Most users expect public.

- Default standalone non-forum threads to ChannelType.PublicThread (11)
- Add optional type field to DiscordThreadCreate for explicit control

Closes #14147

* Discord: honor explicit thread type

* docs(channels): modernize whatsapp docs page (#14202)

* fix(agents): honor heartbeat.model override instead of session model (#14181)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: f19b789057e03d424ee20baf3c678475ad94f72f
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* docs(channels): modernize slack docs page (#14205)

* fix: report subagent timeout as 'timed out' instead of 'completed successfully' (#13996)

* fix: report subagent timeout as 'timed out' instead of 'completed successfully'

* fix: propagate subagent timeout status across agent.wait (#13996) (thanks @dario-github)

---------

Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>

* docs(channels): modernize imessage docs page (#14213)

* fix(cli): exit with non-zero code when configure/agents-add wizards are cancelled (#14156)

* fix(cli): exit with non-zero code when configure/agents-add wizards are cancelled

Follow-up to the onboard cancel fix. The configure wizard and
agents add wizard also caught WizardCancelledError and exited with
code 0, which signals success to callers. Change to exit(1) for
consistency — user cancellation is not a successful completion.

This ensures scripts that chain these commands with set -e will
correctly stop when the user cancels.

* fix(cli): make wizard cancellations exit non-zero (#14156) (thanks @0xRaini)

---------

Co-authored-by: Rain <rain@Rains-MBA-M4.local>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>

* chore: making PR review chores deterministic + less token hungry

* fix(heartbeat): honor heartbeat.model config for heartbeat turns (#14103)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: f46080b0adb882c4d18af7ac0e80055505ff640c
Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* chore: make review mode switching idempotent

* fix(discord): use partial mock for @buape/carbon in slash test

Replace the full manual mock with importOriginal spread so new SDK
exports are available automatically. Only ChannelType, MessageType,
and Client are overridden — the rest come from the real module.

Prevents CI breakage when @buape/carbon adds exports (e.g. the
recent StringSelectMenu failure that blocked unrelated PRs).

Closes #13244

* Changelog: add #13262 entry

* onboard: support custom provider in non-interactive flow (#14223)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 5b98d6514e73f7ee934a350f3b38619c70f49aed
Co-authored-by: ENCHIGO <38551565+ENCHIGO@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix(media): guard local media reads + accept all path types in MEDIA directive

* Memory/QMD: treat plain-text no-results as empty

* Memory/QMD: harden no-results parsing

* fix(agents): exclude rate limit errors from context overflow classification (#13747)

Co-authored-by: 0xRaini <rain@0xRaini.dev>

* fix(ui): escape raw HTML in chat messages instead of rendering it (#13952)

Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>

* fix(agents): re-run tool_use pairing repair after history truncation (#13926)

Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>

* fix(agents): enable tool call ID sanitization for Anthropic provider (#13830)

Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>

* chore: Update deps.

* chore: Cleanup useless CI job.

* chore: Clean up pre-commit hook.

* chore: Remove accidentally committed `.md` file.

* Memory/QMD: add configurable search mode

* Memory: make qmd search-mode flags compatible

* fix(agents): scope process/exec tools to sessionKey for isolation (#4887)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 5d30672e756cc10a6cda90f5bc55cf4812b7d1d6
Co-authored-by: mcinteerj <3613653+mcinteerj@users.noreply.github.com>
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Reviewed-by: @Takhoffman

* CI: add PR size autolabel workflow (#14410)

* (fix): handle Cloudflare 521 and transient 5xx errors gracefully (#13500)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: a8347e95c55c6244bbf2e9066c8bf77bf62de6c9
Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Reviewed-by: @Takhoffman

* fix(cron): prevent duplicate fires when multiple jobs trigger simultaneously (#14256)

The `computeNextRunAtMs` function used `nowSecondMs - 1` as the
reference time for croner's `nextRun()`, which caused it to return the
current second as a valid next-run time. When a job fired at e.g.
11:00:00.500, computing the next run still yielded 11:00:00.000 (same
second, already elapsed), causing the scheduler to immediately re-fire
the job in a tight loop (15-21x observed in the wild).

Fix: use `nowSecondMs` directly (no `-1` lookback) and change the
return guard from `>=` to `>` so next-run is always strictly after
the current second.

Fixes #14164

* fix(cron): re-arm timer when onTimer fires during active job execution (#14233)

* fix(cron): re-arm timer when onTimer fires during active job execution

When a cron job takes longer than MAX_TIMER_DELAY_MS (60s), the clamped
timer fires while state.running is still true.  The early return in
onTimer() previously exited without re-arming the timer, leaving no
setTimeout scheduled.  This silently kills the cron scheduler until the
next gateway restart.

The fix calls armTimer(state) before the early return so the scheduler
continues ticking even when a job is in progress.

This is the likely root cause of recurring cron jobs silently skipping,
as reported in #12025.  One-shot (kind: 'at') jobs were unaffected
because they typically complete within a single timer cycle.

Includes a regression test that simulates a slow job exceeding the
timer clamp period and verifies the next occurrence still fires.

* fix: update tests for timer re-arm behavior

- Update existing regression test to expect timer re-arm with non-zero
  delay instead of no timer at all
- Simplify new test to directly verify state.timer is set after onTimer
  returns early due to running guard

* fix: use fixed 60s delay for re-arm to prevent zero-delay hot-loop

When the running guard re-arms the timer, use MAX_TIMER_DELAY_MS
directly instead of calling armTimer() which can compute a zero delay
for past-due jobs.  This prevents a tight spin while still keeping the
scheduler alive.

* style: add curly braces to satisfy eslint(curly) rule

* fix(cron): isolate schedule errors to prevent one bad job from breaking all jobs (#14385)

Previously, if one cron job had a malformed schedule expression (e.g. invalid cron syntax),
the error would propagate up and break the entire scheduler loop. This meant one misconfigured
job could prevent ALL cron jobs from running.

Changes:
- Wrap per-job schedule computation in try/catch in recomputeNextRuns()
- Track consecutive schedule errors via new scheduleErrorCount field
- Log warnings for schedule errors with job ID and name
- Auto-disable jobs after 3 consecutive schedule errors (with error-level log)
- Clear error count when schedule computation succeeds
- Continue processing other jobs even when one fails

This ensures the scheduler is resilient to individual job misconfigurations while still
providing visibility into problems through logging.

Co-authored-by: Marvin <numegilagent@gmail.com>

* fix(cron): pass agentId to runHeartbeatOnce for main-session jobs (#14140)

* fix(cron): pass agentId to runHeartbeatOnce for main-session jobs

Main-session cron jobs with agentId always ran the heartbeat under
the default agent, ignoring the job's agent binding. enqueueSystemEvent
correctly routed the system event to the bound agent's session, but
runHeartbeatOnce was called without agentId, so the heartbeat ran under
the default agent and never picked up the event.

Thread agentId from job.agentId through the CronServiceDeps type,
timer execution, and the gateway wrapper so heartbeat-runner uses the
correct agent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cron: add heartbeat agentId propagation regression test (#14140) (thanks @ishikawa-pro)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: buffer upload path for feishu SDK (openclaw#10345) thanks @youngerstyle

Co-authored-by: zhiyi <7426274+youngerstyle@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: tighten feishu mention trigger matching (openclaw#11088) thanks @openperf

Co-authored-by: 王春跃 <80630709+openperf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: use resolved feishu account in status probe (openclaw#11233) thanks @onevcat

Co-authored-by: Wei Wang <1019875+onevcat@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: preserve top-level feishu doc block order (openclaw#13994) thanks @Cynosure159

Co-authored-by: Cynosure159 <29699738+Cynosure159@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: remove workspace dev dependency in feishu plugin (openclaw#14423) thanks @jackcooper2015

Co-authored-by: jack-cooper <10961327+jackcooper2015@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* chore: refresh lockfile after feishu dep removal (openclaw#14423) thanks @jackcooper2015

Co-authored-by: jack-cooper <10961327+jackcooper2015@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* chore: add feishu contributor thanks to changelog (openclaw#14448)

Co-authored-by: zhiyi <7426274+youngerstyle@users.noreply.github.com>
Co-authored-by: 王春跃 <80630709+openperf@users.noreply.github.com>
Co-authored-by: Wei Wang <1019875+onevcat@users.noreply.github.com>
Co-authored-by: Cynosure159 <29699738+Cynosure159@users.noreply.github.com>
Co-authored-by: jack-cooper <10961327+jackcooper2015@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(whatsapp): convert Markdown bold/strikethrough to WhatsApp formatting (#14285)

* fix(whatsapp): convert Markdown bold/strikethrough to WhatsApp formatting

* refactor: Move `escapeRegExp` utility function to `utils.js`.

---------

Co-authored-by: Luna AI <luna@coredirection.ai>

* fix: default MIME type for WhatsApp voice messages when Baileys omits it (#14444)

* fix(whatsapp): allow media-only sends and normalize leading blank payloads (#14408)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(cron): use requested agentId for isolated job auth resolution (#13983)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(cron): prevent one-shot at jobs from re-firing on restart after skip/error (#13845) (#13878)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: prevent cron jobs from skipping execution when nextRunAtMs advances (#14068)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* changelog: add cron and whatsapp fix entries with contributor thanks

* fix(telegram): handle no-text message in model picker editMessageText (#14397)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(slack): change default replyToMode from "off" to "all" (#14364)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(tests): update thread ID handling in Slack message collection tests (#14108)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* changelog: add telegram and slack fix entries

* fix(telegram): surface REACTION_INVALID as non-fatal warning (#14340)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* changelog: add telegram reaction warning fix entry

* perf: use JSON.parse instead of JSON5.parse for sessions.json (~35x faster) (#14530)

Co-authored-by: hyf0-agent <hyf0-agent@users.noreply.github.com>

* fix: add types condition to plugin-sdk export for moduleResolution NodeNext (#14485)

Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>

* refactor(config): restore schema.ts to use schema.hints

* fix(config): restore schema.ts schema helper types after hints refactor

* feat(provider): Z.AI endpoints + model catalog (#13456) (thanks @tomsun28) (#13456)

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* feat(telegram): render blockquotes as native <blockquote> tags (#14608) (#14626)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 4a967c51f560fea33694a980bda0d76be6385c71
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight

* fix: BlueBubbles webhook auth bypass via loopback proxy trust (#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.

* fix(gateway): auto-generate token during `gateway install` to prevent launchd restart loop (#13813)

When the gateway is installed as a macOS launch agent and no token is
configured, the service enters an infinite restart loop because launchd
does not inherit shell environment variables. Auto-generate a token
during `gateway install` when auth mode is `token` and no token exists,
matching the existing pattern in doctor.ts and configure.gateway.ts.

The token is persisted to the config file and embedded in the plist
EnvironmentVariables for belt-and-suspenders reliability.

Relates-to: #5103, #2433, #1690, #7749

* fix(media): strip MEDIA: lines with local paths instead of leaking as text (#14399)

When internal tools (e.g. TTS) emit MEDIA:/tmp/... with absolute paths,
isValidMedia() correctly rejects them for security. However, the rejected
MEDIA: line was kept as visible text in the output, leaking the path to
the user.

Now strip MEDIA: lines that look like local paths even when the path
is invalid, so they never appear as user-visible text.

Closes #14365

Co-authored-by: Echo Ito <echoito@MacBook-Air.local>

* fix(gateway): handle async EPIPE on stdout/stderr during shutdown (#13414)

* fix(gateway): handle async EPIPE on stdout/stderr during shutdown

The console capture forward() wrapper catches synchronous EPIPE errors,
but when the receiving pipe closes during shutdown Node emits the error
asynchronously on the stream. Without a listener this becomes an
uncaught exception that crashes the gateway, causing macOS launchd to
permanently unload the service.

Add error listeners on process.stdout and process.stderr inside
enableConsoleCapture() that silently swallow EPIPE/EIO (matching the
existing isEpipeError helper) and re-throw anything else.

Closes #13367

* guard stream error listeners against repeated enableConsoleCapture() calls

Use a separate streamErrorHandlersInstalled flag in loggingState so that
test resets of consolePatched don't cause listener accumulation on
process.stdout/stderr.

* fix: prevent undefined token in gateway auth config (#13809)

- Guard against undefined/empty token in buildGatewayAuthConfig
- Automatically generate random token when token param is undefined, empty, or whitespace
- Prevents JSON.stringify from writing literal string "undefined" to config
- Add tests for undefined, empty, and whitespace token cases

Fixes #13756

Co-authored-by: Klawd Asklee <klawdebot@gmail.com>

* fix(voice-call): pass Twilio stream auth token via <Parameter> instead of query string (#14029)

Twilio strips query parameters from WebSocket URLs in <Stream> TwiML,
so the auth token set via ?token=xxx never arrives on the WebSocket
connection. This causes stream rejection when token validation is enabled.

Fix: pass the token as a <Parameter> element inside <Stream>, which
Twilio delivers in the start message's customParameters field. The
media stream handler now extracts the token from customParameters,
falling back to query string for backwards compatibility.

Co-authored-by: McWiggles <mcwigglesmcgee@users.noreply.github.com>

* fix: exclude maxTokens from config redaction + honor deleteAfterRun on skipped cron jobs (#13342)

* fix: exclude maxTokens and token-count fields from config redaction

The /token/i regex in SENSITIVE_KEY_PATTERNS falsely matched fields like
maxTokens, maxOutputTokens, maxCompletionTokens etc. These are numeric
config fields for token counts, not sensitive credentials.

Added a whitelist (SENSITIVE_KEY_WHITELIST) that explicitly excludes
known token-count field names from redaction. This prevents config
corruption when maxTokens gets replaced with __OPENCLAW_REDACTED__
during config round-trips.

Fixes #13236

* fix: honor deleteAfterRun for one-shot 'at' jobs with 'skipped' status

Previously, deleteAfterRun only triggered when result.status was 'ok'.
For one-shot 'at' jobs, a 'skipped' status (e.g. empty heartbeat file)
would leave the job in state but disabled, never getting cleaned up.

Now deleteAfterRun also triggers on 'skipped' status for 'at' jobs,
since a skipped one-shot job has no meaningful retry path.

Fixes #13249

* Cron: format timer.ts

---------

Co-authored-by: nice03 <niceyslee@gmail.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(gateway): drain active turns before restart to prevent message loss (#13931)

* fix(gateway): drain active turns before restart to prevent message loss

On SIGUSR1 restart, the gateway now waits up to 30s for in-flight agent
turns to complete before tearing down the server. This prevents buffered
messages from being dropped when config.patch or update triggers a restart
while agents are mid-turn.

Changes:
- command-queue.ts: add getActiveTaskCount() and waitForActiveTasks()
  helpers to track and wait on active lane tasks
- run-loop.ts: on restart signal, drain active tasks before server.close()
  with a 30s timeout; extend force-exit timer accordingly
- command-queue.test.ts: update imports for new exports

Fixes #13883

* fix(queue): snapshot active tasks for restart drain

---------

Co-authored-by: Elonito <0xRaini@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: Unauthenticated Nostr profile API allows remote config tampering (#13719)

* fix(an-07): apply security fix

Generated by staged fix workflow.

* fix(an-07): apply security fix

Generated by staged fix workflow.

* fix(an-07): satisfy lint in plugin auth regression test

Replace unsafe unknown-to-string coercion in the gateway plugin auth test helper with explicit string/null/JSON handling so pnpm check passes.

* fix: ignore meta field changes in config file watcher (#13460)

Prevents infinite restart loop when gateway updates meta.lastTouchedAt
and meta.lastTouchedVersion on startup.

Fixes #13458

* fix(daemon): suppress EPIPE error in restartLaunchAgent stdout write (#14343)

After a successful launchctl kickstart, the stdout.write() for the
status message may fail with EPIPE if the receiving end has already
closed. Catch and ignore EPIPE specifically; re-throw other errors.

Closes #14234

Co-authored-by: Echo Ito <echoito@MacBook-Air.local>

* fix: prevent double compaction caused by cache-ttl entry bypassing guard (#13514)

Move appendCacheTtlTimestamp() to after prompt + compaction retry
completes instead of before. The previous placement inserted a custom
entry (openclaw.cache-ttl) between compaction and the next prompt,
which broke pi-coding-agent's prepareCompaction() guard — the guard
only checks if the last entry is type 'compaction', and the cache-ttl
custom entry made it type 'custom', allowing an immediate second
compaction at very low token counts (e.g. 5,545 tokens) that nuked
all preserved context.

Fixes #9282
Relates to #12170

* feat: support .agents/skills/ directory for cross-agent skill discovery (#9966)

Adds loading from two .agents/skills/ locations:
- ~/.agents/skills/ (personal/user-level, source "agents-skills-personal")
- {workspace}/.agents/skills/ (project-level, source "agents-skills-project")

Precedence: extra < bundled < managed < personal .agents/skills < project .agents/skills < workspace.

Closes #8822

* fix(antigravity): opus 4.6 forward-compat model + thinking signature sanitization bypass (#14218)

Two fixes for Google Antigravity (Cloud Code Assist) reliability:

1. Forward-compat model fallback: pi-ai's model registry doesn't include
   claude-opus-4-6-thinking. Add resolveAntigravityOpus46ForwardCompatModel()
   that clones the opus-4-5 template so the correct api ("google-gemini-cli")
   and baseUrl are preserved. Fixes #13765.

2. Fix thinking.signature rejection: The API returns Claude thinking blocks
   without signatures, then rejects them on replay. The existing sanitizer
   strips unsigned blocks, but the orphaned-user-message path in attempt.ts
   bypassed it by reading directly from disk. Now applies
   sanitizeAntigravityThinkingBlocks at that code path.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Fix: Prevent file descriptor leaks in child process cleanup (#13565)

* fix: prevent FD leaks in child process cleanup

- Destroy stdio streams (stdin/stdout/stderr) after process exit
- Remove event listeners to prevent memory leaks
- Clean up child process reference in moveToFinished()
- Also fixes model override handling in agent.ts

Fixes EBADF errors caused by accumulating file descriptors
from sub-agent spawns.

* Fix: allow stdin destroy in process registry cleanup

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix: use last API call's cache tokens for context-size display (#13698) (#13805)

The UsageAccumulator sums cacheRead/cacheWrite across all API calls
within a single turn. With Anthropic prompt caching, each call reports
cacheRead ≈ current_context_size, so after N tool-call round-trips the
accumulated total becomes N × actual_context, which gets clamped to
contextWindow (200k) by deriveSessionTotalTokens().

Fix: track the most recent API call's cache fields separately and use
them in toNormalizedUsage() for context-size reporting. This makes
/status Context display accurate while preserving accumulated output
token counts.

Fixes #13698
Fixes #13782

Co-authored-by: akari-musubi <259925157+akari-musubi@users.noreply.github.com>

* changelog: add missing fix entries

* fix(agents): narrow billing error 402 regex to avoid false positives on issue IDs (#13827)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: b0501bbab7b3ec3ed56eb8903d9a27f8273f0edb
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight

* fix(runtime): guard cleanup and preserve skipped cron jobs

* AGENTS.md: make PR_WORKFLOW optional (don’t override maintainer workflows)

* fix(telegram): add retry logic to health probe (openclaw#7405) thanks @mcinteerj

Verified:
- CI=true pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: mcinteerj <3613653+mcinteerj@users.noreply.github.com>

* fix: fix(boot): use ephemeral session per boot to prevent stale context (openclaw#11764) thanks @mcinteerj

Verified:
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: mcinteerj <3613653+mcinteerj@users.noreply.github.com>

* fix(tts): strip markdown before sending text to TTS engines (#13237)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 163c68539f672eaa81cca9388573a12b39eb1b58
Co-authored-by: danielwanwx <144515713+danielwanwx@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight

* fix: fix: transcribe audio before mention check in groups with requireMention (openclaw#9973) thanks @mcinteerj

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: mcinteerj <3613653+mcinteerj@users.noreply.github.com>

* fix(gateway): increase WebSocket max payload to 5 MB for image uploads (#14486)

* fix(gateway): increase WebSocket max payload to 5 MB for image uploads

The 512 KB limit was too small for base64-encoded images — a 400 KB
image becomes ~532 KB after encoding, exceeding the limit and closing
the connection with code 1006.

Bump MAX_PAYLOAD_BYTES to 5 MB and MAX_BUFFERED_BYTES to 8 MB to
support standard image uploads via webchat.

Closes #14400

* fix: align gateway WS limits with 5MB image uploads (#14486) (thanks @0xRaini)

* docs: fix changelog conflict for #14486

---------

Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* perf(test): speed up vitest by skipping plugins + LLM slug

* perf(test): add vitest slowest report artifact

* refactor(test): consolidate infra unit tests

* test(agents): make grok api key test hermetic

* chore: bump version to 2026.2.12

* fix(cli): guard against read-only process.noDeprecation on Node.js v23+ (#14152)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 11bb9f141ae01d85c7eb8d4f8b526d7bda419558
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete

* fix: remove bundled soul-evil hook (closes #8776) (#14757)

* fix: remove bundled soul-evil hook (closes #8776)

* fix: remove soul-evil docs (#14757) (thanks @Imccccc)

---------

Co-authored-by: OpenClaw Bot <bot@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(security): add --ignore-scripts to skills install commands (#14659)

Skills install runs package manager install commands (npm, pnpm, yarn,
bun) without --ignore-scripts, allowing malicious npm packages to
execute arbitrary code via postinstall/preinstall lifecycle scripts
during global installation.

This is inconsistent with the security fix in commit 92702af7a which
added --ignore-scripts to both plugin installs (src/plugins/install.ts)
and hook installs (src/hooks/install.ts). Skills install was overlooked
in that change.

Global install (-g) is particularly dangerous as scripts execute with
the user's full permissions and can modify globally-accessible binaries.

* perf(test): speed up test runs and harden temp cleanup

* fix(feishu): use msg_type 'media' for video/audio messages (#14648)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: e8044cb2085cc77ac2b9e819a09dc7e1c21bc8da
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete

* fix: use local timezone in console log timestamps

formatConsoleTimestamp previously used Date.toISOString() which always
returns UTC time (suffixed with Z). This confused users whose local
timezone differs from UTC.

Now uses local time methods (getHours, getMinutes, etc.) and appends the
local UTC offset (e.g. +08:00) instead of Z. The pretty style returns
local HH:MM:SS. The hasTimestampPrefix regex is updated to accept both
Z and +/-HH:MM offset suffixes.

Closes #14699

* fix: local-time timestamps include offset (#14771) (thanks @0xRaini)

* feat(zai): auto-detect endpoint + default glm-5 (#14786)

* feat(zai): auto-detect endpoint + default glm-5

* test: fix Z.AI default endpoint expectation (#14786)

* test: bump embedded runner beforeAll timeout

* chore: update changelog for Z.AI GLM-5 autodetect (#14786)

* chore: resolve changelog merge conflict with main (#14786)

* chore: append changelog note for #14786 without merge conflict

* chore: sync changelog with main to resolve merge conflict

* fix(providers): include provider name in billing error messages (#14697)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 774e0b660514d59fea48bda0e300e94b398f58e8
Co-authored-by: fagemx <117356295+fagemx@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd

* fix: resolve symlinked argv1 for Control UI asset detection (#14919)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 07b85041dc70b5839247dc661f123ff37b745c1c
Co-authored-by: gumadeiras <116837+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* docs(changelog): add Control UI symlink install fix entry

Co-authored-by: aynorica <54416476+aynorica@users.noreply.github.com>

* chore: refining review PR additional prompts

* chore(pr-skills): suppress output for successful commands (pnpm install/build/test/etc) to lower context usage

* chore: move local imports to the top

* fix(agents): guard against undefined path in context file entries (#14903)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 25856b863d62eda20720db53fea43cbf213b5cc5
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* CI: add contributor tier labels

* fix: prevent heartbeat scheduler death when runOnce throws (#14901)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 022efbfef959f4c4225d7ab1a49540c8f39accd3
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* CI: add labeler backfill dispatch

* CI: handle search 422 in labeler

* fix: use os.tmpdir fallback paths for temp files (#14985)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 347c689407037a05be0717209660076c6a07d0ec
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* Scripts: add issue labeler state + PR support

* CI: close PRs with excessive labels

* CI: gate auto-response with trigger label

* Browser/Logging: share default openclaw tmp dir resolver

* feat(minimax): update models from M2.1 to M2.5 (#14865)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 1d58bc5760af657e205f7a113cec30aaf461abc6
Co-authored-by: adao-max <153898832+adao-max@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* chore(deps): update dependencies

* fix(irc): type socket error param

* Tests: make download temp-path assertion cross-platform

* chore: fix windows CI tests

* fix: respect session model override in agent runtime (#14783) (#14983)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: ec47d1a7bf4e97a5db77281567318c1565d319b5
Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* Discord: handle thread edit params

* fix(discord): add TTL and LRU eviction to thread starter cache

Fixes #5260

The DISCORD_THREAD_STARTER_CACHE Map was growing unbounded during
long-running gateway sessions, causing memory exhaustion.

This fix adds:
- 5-minute TTL expiry (thread starters rarely change)
- Max 500 entries with LRU eviction
- Same caching pattern used by Slack's thread resolver

The implementation mirrors src/slack/monitor/thread-resolution.ts
which already handles this correctly.

* fix: use iterator.done check for LRU eviction

Fixes edge case where empty string key would stop eviction early

* fix(signal): replace  with @uuid/@phone from mentions

Related #1926

Signal mentions were appearing as  (object replacement character)
instead of readable identifiers. This caused Clawdbot to misinterpret
messages and respond inappropriately.

Now parses dataMessage.mentions array and replaces the placeholder
character with @{uuid} or @{phone} from the mention metadata.

* Signal: normalize mention placeholders

* Signal: satisfy lint

* fix: normalize Signal mentions (#2013) (thanks @alexgleason)

* Discord: preserve media caption whitespace

* fix: document Discord media-only messages (#9507) (thanks @leszekszpunar)

* fix: add Discord channel-edit thread params (#5542) (thanks @stumct)

* fix: process Discord DM reactions instead of silently dropping them

* fix: use proper LoadedConfig type in test mock

* fix: use canonical 'direct' instead of 'dm' for DM peer kind (fixes TS2322)

* fix: handle discord dm reaction allowlist

* Changelog: note discord dm reaction fix

* fix: update totalTokens after compaction using last-call usage (#15018)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 9214291bf7e9e62ba8661aa46b4739113794056a
Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* chore: make changelog mandatory in PR skills

* Signal: validate account input

* Changelog: credit Signal account validation

* Signal: harden E.164 validation

* fix(security): harden untrusted web tool transcripts

* fix(agents): stabilize overflow compaction retries and session context accounting (openclaw#14102) thanks @vpesh

Verified:
- CI checks for commit 86a7ecb45ebf0be61dce9261398000524fd9fab6
- Rebase conflict resolution for compatibility with latest main

Co-authored-by: vpesh <9496634+vpesh@users.noreply.github.com>

* fix: wire 9 unwired plugin hooks to core code (openclaw#14882) thanks @shtse8

Verified:
- GitHub CI checks green (non-skipped)

Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>

* fix: prevent ghost reminder notifications (#13317)

The heartbeat runner was incorrectly triggering CRON_EVENT_PROMPT
whenever ANY system events existed during a cron heartbeat, even if
those events were unrelated (e.g., HEARTBEAT_OK acks, exec completions).

This caused phantom 'scheduled reminder' notifications with no actual
reminder content.

Fix: Only treat as cron event if pending events contain actual
cron-related messages, excluding standard heartbeat acks and
exec completion messages.

Fixes #13317

* test: add test for ghost reminder bug (#13317)

* test: fix test isolation and assertion issues

- Add resetSystemEventsForTest() in beforeEach/afterEach
- Fix hardcoded status assertions (use toBeDefined + conditional checks)
- Prevents cross-test pollution of global system event queue

Addresses Greptile feedback on PR #15059

* feat: embed actual event text in cron prompt

Combines two complementary fixes for ghost reminder bug:

1. Filter HEARTBEAT_OK/exec messages (previous commit)
2. Embed actual event content in prompt (this commit)

Instead of static 'shown above' message, dynamically build prompt
with actual reminder text. Ensures model sees event content directly.

Credit: Approach inspired by @nyx-rymera's analysis in #13317

Fixes #13317

* fix: refine cron heartbeat event detection

* changelog: keep signal entry while restoring removed rows

* changelog: dedupe signal entry restored by merge conflict fix

* fix: align cron prompt content with filtered reminder events

* fix(security): harden hook and device token auth

* fix: confine sandbox skill sync destinations

* fix: harden session transcript path resolution

* fix: harden OpenResponses URL input fetching

* chore: Update deps.

* fix(memory-flush): instruct agents to append rather than overwrite memory files (openclaw#6878) thanks @EmberCF

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test (fails on unrelated existing telegram test file)

Co-authored-by: EmberCF <258471336+EmberCF@users.noreply.github.com>

* fix: dispatch before_tool_call and after_tool_call hooks from both tool execution paths (openclaw#15012) thanks @Patrick-Barletta

Verified:
- pnpm check

Co-authored-by: Patrick-Barletta <67929313+Patrick-Barletta@users.noreply.github.com>

* fix(discord): respect replyToMode in thread channel

* fix(discord): replyToMode first behaviour

* fix: update replyToMode notes (#11062) (thanks @cordx56)

* Changelog: add missing entries for #14882 and #15012

* fix(media): strip audio attachments after successful transcription (openclaw#9076) thanks @nobrainer-tech

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test (fails in known unrelated telegram suite)
- pnpm vitest run src/auto-reply/media-note.test.ts src/auto-reply/reply.media-note.test.ts

Co-authored-by: nobrainer-tech <445466+nobrainer-tech@users.noreply.github.com>

* fix: preserve inter-session input provenance (thanks @anbecker)

* fix(browser): require auth on control HTTP and auto-bootstrap token

* docs: add Windows installer debug equivalents

* fix: harden hook session key routing defaults

* test: stabilize telegram media timing tests

* fix: remove accidental root package-lock.json (#15102)

* fix: include plugin sdk dts tsconfig in onboard docker image

* fix: emit message_sent hook for all successful outbound paths (#15104)

* fix: add adapter-path after_tool_call coverage (follow-up to #15012) (#15105)

* fix(ci): resolve windows test path assertion and sync protocol swift models

* Discord: implement role allowlist with OR logic in preflight

* Discord: implement role-based agent routing in resolveAgentRoute

* Discord: pass member role IDs to agent route resolution

* Discord: add unit tests for role-based agent routing

* fix: add curly braces to resolve-route.ts for eslint(curly) compliance

* fix: use member.roles as string[] per Discord API types

* fix: add missing role-based type definitions for RBAC routing

* fix: exclude role-restricted bindings from guild-only matching

* fix: add discord role allowlists (#10650) (thanks @Minidoracat)

* Discord: honor Administrator in permission checks

* Changelog: note Discord admin permission fix

* CLI: add plugins uninstall command (#5985) (openclaw#6141) thanks @JustasMonkev

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: JustasMonkev <59362982+JustasMonkev@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(browser): hide navigator.webdriver from reCAPTCHA v3 detection (openclaw#10735) thanks @Milofax

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: Milofax <2537423+Milofax@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* feat: expose /compact command in Telegram native menu (openclaw#10352) thanks @akramcodez

Verified:
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: akramcodez <179671552+akramcodez@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* chore: update appcast for 2026.2.12

* feat(feishu): add streaming card support via Card Kit API (openclaw#10379) thanks @xzq-xu

Verified:
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: xzq-xu <53989315+xzq-xu@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(onboarding): exit cleanly after web ui hatch

* fix(session): preserve verbose/thinking/tts overrides across /new and /reset (openclaw#10881) thanks @mcaxtr

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(macos): prevent Voice Wake crash on CJK trigger transcripts (openclaw#11052) thanks @Flash-LHR

Verified:
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: Flash-LHR <47357603+Flash-LHR@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(update): repair daemon-cli compat exports after self-update

* chore(release): bump version to 2026.2.13

* fix(security): prevent String(undefined) coercion in credential inputs (#12287)

* fix(security): prevent String(undefined) coercion in credential inputs

When a prompter returns undefined (due to cancel, timeout, or bug),
String(undefined).trim() produces the literal string "undefined" instead
of "". This truthy string prevents secure fallbacks from triggering,
allowing predictable credential values (e.g., gateway password = "undefined").

Fix all 8 occurrences by using String(value ?? "").trim(), which correctly
yields "" for null/undefined inputs and triggers downstream validation or
fallback logic.

Fixes #8054

* fix(security): also fix String(undefined) in api-provider credential inputs

Address codex review feedback: 4 additional occurrences of the unsafe
String(variable).trim() pattern in auth-choice.apply.api-providers.ts
(Cloudflare Account ID, Gateway ID, synthetic API key inputs + validators).

* fix(test): strengthen password coercion test per review feedback

* fix(security): harden credential prompt coercion

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix: prevent heartbeat scheduler silent death from wake handler race (#15108)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: fd7165b93547251c48904fa60b4b608d96bfb65c
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix(sandbox): pass docker.env into sandbox container

* fix: pass sandbox docker env into containers (#15138) (thanks @stevebot-alive)

* fix(cli): use raw config instead of runtime-merged config in config set/unset

Fixes #6070

The config set/unset commands were using snapshot.config (which contains
runtime-merged defaults) instead of snapshot.parsed (the raw user config).
This caused runtime defaults like agents.defaults to leak into the written
config file when any value was set or unset.

Changed both set and unset commands to use structuredClone(snapshot.parsed)
to preserve only user-specified config values.

* fix(config): add resolved field to ConfigFileSnapshot for pre-defaults config

The initial fix using snapshot.parsed broke configs with $include directives.
This commit adds a new 'resolved' field to ConfigFileSnapshot that contains
the config after $include and ${ENV} substitution but BEFORE runtime defaults
are applied. This is now used by config set/unset to avoid:
1. Breaking configs with $include directives
2. Leaking runtime defaults into the written config file

Also removes applyModelDefaults from writeConfigFile since runtime defaults
should only be applied when loading, not when writing.

* fix(config): redact resolved field in config snapshots

The newly added 'resolved' field contains secrets after ${ENV}
substitution. This commit ensures redactConfigSnapshot also redacts
the resolved field to prevent credential leaks in config.get responses.

* fix(config): enforce default-free persistence in write path

* fix(exec): allow heredoc operator (<<) in allowlist security mode (#13811)

* fix(exec): allow heredoc operator (<<) in allowlist security mode

* fix: allow multiline heredoc parsing in exec approvals (#13811) (thanks @mcaxtr)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(security): distinguish webhooks from internal hooks in audit summary (#13474)

* fix(security): distinguish webhooks from internal hooks in audit summary

The attack surface summary reported a single 'hooks: disabled/enabled' line
that only checked the external webhook endpoint (hooks.enabled), ignoring
internal hooks (hooks.internal.enabled). Users who enabled internal hooks
(session-memory, command-logger, etc.) saw 'hooks: disabled' and thought
something was broken.

Split into two separate lines:
- hooks.webhooks: disabled/enabled
- hooks.internal: disabled/enabled

Fixes #13466

* test(security): move attack surface tests to focused test file

Move the 3 new hook-distinction tests from the monolithic audit.test.ts
(1,511 lines) into a dedicated audit-extra.sync.test.ts that tests
collectAttackSurfaceSummaryFindings directly. Avoids growing the
already-large test file and keeps tests focused on the changed unit.

* fix: add changelog entry for security audit hook split (#13474) (thanks @mcaxtr)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix(docs): remove hardcoded Mermaid init blocks that break dark mode (#15157)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 3239baaf150f451328d86a0e054ab4a8de264e30
Co-authored-by: heytulsiprasad <52394293+heytulsiprasad@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight

* fix: thread replyToId and threadId through message tool send action (#14948)

* fix: thread replyToId and threadId through message tool send action

* fix: omit replyToId/threadId from gateway send params

* fix: add threading seam regression coverage (#14948) (thanks @mcaxtr)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix: replace file-based session store lock with in-process Promise chain mutex (#14498)

* fix: replace file-based session store lock with in-process Promise chain mutex

Node.js is single-threaded, so file-based locking (open('wx') + polling +
stale eviction) is unnecessary and causes timeouts under heavy session load.

Replace with a simple per-storePath Promise chain that serializes access
without any filesystem overhead.

In a 1159-session environment over 3 hours:
- Lock timeouts: 25
- Stuck sessions: 157 (max 1031s, avg 388s)
- Slow listeners: 39 (max 265s, avg 70s)

Root cause: during sessions.json file I/O, await yields control and other
lock requests hit the 10s timeout waiting for the .lock file to be released.

* test: add comprehensive tests for Promise chain mutex lock

- Concurrent access serialization (10 parallel writers, counter integrity)
- Error resilience (single & multiple consecutive throws don't poison queue)
- Independent storePath parallelism (different paths run concurrently)
- LOCK_QUEUES cleanup after completion and after errors
- No .lock file created on disk

Also fix: store caught promise in LOCK_QUEUES to avoid unhandled rejection
warnings when queued fn() throws.

* fix: add timeout to Promise chain mutex to prevent infinite hangs on Windows

* fix(session-store): enforce strict queue timeout + cross-process lock

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* feat(ui): add RTL support for Hebrew/Arabic text in webchat (openclaw#11498) thanks @dirbalak

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test

Co-authored-by: dirbalak <30323349+dirbalak@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* fix(sandbox): force network bridge for browser container (#6961)

* feat(slack): include thread metadata (thread_ts, parent_user_id) in agent context

Adds thread_ts and parent_user_id to the Slack message footer for thread
replies, giving agents awareness of thread context. Top-level messages
remain unchanged.

Includes tests verifying:
- Thread replies include thread_ts and parent_user_id in footer
- Top-level messages exclude thread metadata

* fix: align slack thread footer metadata with reply semantics (#14625) (thanks @bennewton999)

* fix(outbound): return error instead of silently redirecting to allowList[0] (#13578)

* fix(reply): auto-inject replyToCurrent for reply threading

replyToMode "first"/"all" only filters replyToId but never generates
it — that required the LLM to emit [[reply_to_current]] tags. Inject
replyToCurrent:true on all payloads so applyReplyTagsToPayload sets
replyToId=currentMessageId, then let the existing mode filter decide
which replies keep threading (first only, all, or off).

Covers both final reply path (reply-payloads.ts) and block streaming
path (agent-runner-execution.ts).

* fix: preserve off-mode semantics in auto reply threading (#14976) (thanks @Diaspar4u)

* Auto-reply: fix non-default agent session transcript path resolution (#15154)

* Auto-reply: fix non-default agent transcript path resolution

* Auto-reply: harden non-default agent transcript lookups

* Auto-reply: harden session path resolution across agent stores

* test: speed up test suite and trim redundant onboarding tests

* fix: stabilize test runner and daemon-cli compat

* fix(aa-01): apply security fix

Generated by staged fix workflow.

* fix: enforce feishu dm policy + pairing flow (#14876) (thanks @coygeek)

* fix(slack): populate thread session with existing thread history (#7610)

* feat(slack): populate thread session with existing thread history

When a new session is created for a Slack thread, fetch and inject
the full thread history as context. This preserves conversation
continuity so the bot knows what it previously said in the thread.

- Add resolveSlackThreadHistory() to fetch all thread messages
- Add ThreadHistoryBody to context payload
- Use thread history instead of just thread starter for new sessions

Fixes #4470

* chore: remove redundant comments

* fix: use threadContextNote in queue body

* fix(slack): address Greptile review feedback

- P0: Use thread session key (not base session key) for new-session check
  This ensures thread history is injected when the thread session is new,
  even if the base channel session already exists.

- P1: Fetch up to 200 messages and take the most recent N
  Slack API returns messages in chronological order (oldest first).
  Previously we took the first N, now we take the last N for relevant context.

- P1: Batch resolve user names with Promise.all
  Avoid N sequential API calls when resolving user names in thread history.

- P2: Include file-only messages in thread history
  Messages with attachments but no text are now included with a placeholder
  like '[attached: image.png, document.pdf]'.

- P2: Add documentation about intentional 200-message fetch limit
  Clarifies that we intentionally don't paginate; 200 covers most threads.

* style: add braces for curly lint rule

* feat(slack): add thread.initialHistoryLimit config option

Allow users to configure the maximum number of thread messages to fetch
when starting a new thread session. Defaults to 20. Set to 0 to disable
thread history fetching entirely.

This addresses the optional configuration request from #2608.

* chore: trigger CI

* fix(slack): ensure isNewSession=true on first thread turn

recordInboundSession() in prepare.ts creates the thread session entry
before session.ts reads the store, causing isNewSession to be false
on the very first user message in a thread. This prevented thread
context (history/starter) from being injected.

Add IsFirstThreadTurn flag to message context, set when
readSessionUpdatedAt() returns undefined for the thread session key.
session.ts uses this flag to force isNewSession=true.

* style: format prepare.ts for oxfmt

* fix: suppress InboundHistory/ThreadStarterBody when ThreadHistoryBody present (#13912)

When ThreadHistoryBody is fetched from the Slack API (conversations.replies),
it already contains pending messages and the thread starter. Passing both
InboundHistory and ThreadStarterBody alongside ThreadHistoryBody caused
duplicate content in the LLM context on new thread sessions.

Suppress InboundHistory and ThreadStarterBody when ThreadHistoryBody is
present, since it is a strict superset of both.

* remove verbose comment

* fix(slack): paginate thread history context fetch

* fix(slack): wire session file path options after main merge

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>

* fix: /status shows incorrect context percentage — totalTokens clamped to contextTokens (#15114) (#15133)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: a489669fc7e86db03484ef5a0ae222d9360e72f7
Co-authored-by: echoVic <16428813+echoVic@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix(ci): sync extension versions to root release (#15199)

* test: stabilize local-timestamp assertion in session resets

* fix: update systemd unit version on gateway restart

When restarting the gateway via restartSystemdService(), the systemd unit
file's Description and OPENCLAW_SERVICE_VERSION are now checked against
the running version. If stale, they are patched and daemon-reload is run
before restarting.

Previously, only `openclaw gateway install` wrote the version to the
unit file. After upgrading (git pull + rebuild, or pnpm update), a
restart would leave the old version baked into the service description
and environment.

Fixes #16012

AI-assisted: written with Claude (OpenClaw agent), fully understood and reviewed.

* test(memory): add failing engram backend config and manager contract specs

* feat(memory): wire native engram backend with builtin fallback

* ci: add GitHub Actions CI workflow (#3)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: ryan-crabbe <128659760+ryan-crabbe@users.noreply.github.com>
Co-authored-by: Rain <rain@RaindeMacBook-Air.local>
Co-authored-by: Sk Akram <skcodewizard786@gmail.com>
Co-authored-by: Peter Lee <spymlixia@hotmail.com>
Co-authored-by: xialonglee <li.xialong@xydigit.com>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
Co-authored-by: constansino <65108260+constansino@users.noreply.github.com>
Co-authored-by: Kyle Tse <shtse8@gmail.com>
Co-authored-by: shtse8 <8020099+shtse8@users.noreply.github.com>
Co-authored-by: J young Lee <beefiker@gmail.com>
Co-authored-by: beefiker <55247450+beefiker@users.noreply.github.com>
Co-authored-by: gumadeiras <gumadeiras@gmail.com>
Co-authored-by: Shadow <hi@shadowing.dev>
Co-authored-by: Rain <rain@Rains-MBA-M4.local>
Co-authored-by: 0xRain <tianrun.yang@hotmail.com>
Co-authored-by: 0xRaini <190923101+0xRaini@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: Dario Zhang <zdclink@gmail.com>
Co-authored-by: Claude Code <claude@anthropic.com>
Co-authored-by: ENCHIGO <38551565+ENCHIGO@users.noreply.github.com>
Co-authored-by: buddyh <31752869+buddyh@users.noreply.github.com>
Co-authored-by: Vignesh Natarajan <vigneshnatarajan92@gmail.com>
Co-authored-by: 0xRaini <rain@0xRaini.dev>
Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com>
Co-authored-by: cpojer <christoph.pojer@gmail.com>
Co-authored-by: Jake <mcinteerj@gmail.com>
Co-authored-by: mcinteerj <3613653+mcinteerj@users.noreply.github.com>
Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
Co-authored-by: Rodrigo Uroz <rodrigouroz@gmail.com>
Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Xinhua Gu <xinhua.gu@gmail.com>
Co-authored-by: Tom Ron <126325152+tomron87@users.noreply.github.com>
Co-authored-by: MarvinDontPanic <numegil.agent@gmail.com>
Co-authored-by: Marvin <numegilagent@gmail.com>
Co-authored-by: 石川 諒 <ishikawa.pro@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: zhiyi <7426274+youngerstyle@users.noreply.github.com>
Co-authored-by: 王春跃 <80630709+openperf@users.noreply.github.com>
Co-authored-by: Wei Wang <1019875+onevcat@users.noreply.github.com>
Co-authored-by: Cynosure159 <29699738+Cynosure159@users.noreply.github.com>
Co-authored-by: jack-cooper <10961327+jackcooper2015@users.noreply.github.com>
Co-authored-by: Ajay Rajnikanth <ajay.rajnikanth@gmail.com>
Co-authored-by: Luna AI <luna@coredirection.ai>
Co-authored-by: Marcus Castro <mcaxtr@gmail.com>
Co-authored-by: Karim Naguib <kn.karimnaguib@gmail.com>
Co-authored-by: 大猫子 <ll1042668699@gmail.com>
Co-authored-by: WalterSumbon <45062253+WalterSumbon@users.noreply.github.com>
Co-authored-by: NM <neeravmakwana@gmail.com>
Co-authored-by: J. Brandon Johnson <b@exct.io>
Co-authored-by: hyf0-agent <ada.20260202@outlook.com>
Co-authored-by: hyf0-agent <hyf0-agent@users.noreply.github.com>
Co-authored-by: Tomsun28 <tomsun28@outlook.com>
Co-authored-by: lailoo <20536249+lailoo@users.noreply.github.com>
Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
Co-authored-by: Cathryn Lavery <50469282+cathrynlavery@users.noreply.github.com>
Co-authored-by: Echo Ito <echoito@MacBook-Air.local>
Co-authored-by: Keshav Rao <keshavrao250@gmail.com>
Co-authored-by: asklee-klawd <105007315+asklee-klawd@users.noreply.github.com>
Co-authored-by: Klawd Asklee <klawdebot@gmail.com>
Co-authored-by: mcwigglesmcgee <mcwigglesmcgee@gmail.com>
Co-authored-by: McWiggles <mcwigglesmcgee@users.noreply.github.com>
Co-authored-by: niceysam <niceysam03@gmail.com>
Co-authored-by: nice03 <niceyslee@gmail.com>
Co-authored-by: brandonwise <brandonawise@gmail.com>
Co-authored-by: taw0002 <42811278+taw0002@users.noreply.github.com>
Co-authored-by: Taras Lukavyi <lukavyi@me.com>
Co-authored-by: jg-noncelogic <jg@noncelogic.com>
Co-authored-by: Kyle Chen <ky1echen@foxmail.com>
Co-authored-by: Akari <musubiakari@gmail.com>
Co-authored-by: akari-musubi <259925157+akari-musubi@users.noreply.github.com>
Co-authored-by: danielwanwx <144515713+danielwanwx@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Co-authored-by: Tyler <zqasder@gmail.com>
Co-authored-by: OpenClaw Bot <bot@openclaw.ai>
Co-authored-by: Yi Liu <yi@quantstamp.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: fagemx <117356295+fagemx@users.noreply.github.com>
Co-authored-by: shakkernerd <165377636+shakkernerd@users.noreply.github.com>
Co-authored-by: gumadeiras <116837+gumadeiras@users.noreply.github.com>
Co-authored-by: aynorica <54416476+aynorica@users.noreply.github.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@users.noreply.github.com>
Co-authored-by: Joseph Krug <joeykrug@gmail.com>
Co-authored-by: joeykrug <5925937+joeykrug@users.noreply.github.com>
Co-authored-by: Skyler Miao <153898832+adao-max@users.noreply.github.com>
Co-authored-by: Web Vijayi <hello@webvijayi.com>
Co-authored-by: Alex Gleason <alex@alexgleason.me>
Co-authored-by: Vladimir Peshekhonov <ardin.cal@gmail.com>
Co-authored-by: vpesh <9496634+vpesh@users.noreply.github.co…
cloud-neutral pushed a commit to cloud-neutral-toolkit/openclawbot.svc.plus that referenced this pull request Feb 15, 2026
…aw#13787)

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): apply security fix

Generated by staged fix workflow.

* fix(an-08): stabilize bluebubbles auth fixture for security patch

Restore the default test password in createMockAccount and add a
fallback password query in createMockRequest when auth is omitted.

This keeps the AN-08 loopback-auth regression tests strict while
preserving existing monitor behavior tests that assume authenticated
webhook fixtures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: bluebubbles Channel integration: bluebubbles

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: BlueBubbles webhook auth bypass via loopback proxy trust

4 participants