Skip to content

fix(gateway): preserve runtime-backed health state#72417

Merged
vincentkoc merged 3 commits into
mainfrom
clownfish/ghcrawl-207035-agentic-merge
Apr 27, 2026
Merged

fix(gateway): preserve runtime-backed health state#72417
vincentkoc merged 3 commits into
mainfrom
clownfish/ghcrawl-207035-agentic-merge

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • repair fix(gateway): preserve runtime-backed health state #39921 as the canonical runtime-backed gateway health fix
  • preserve live channel/account runtime fields in health summaries and gateway.health cache/broadcast refreshes
  • keep probe behavior explicit and preserve probe payloads when plugin snapshot builders omit them
  • guard runtime snapshot capture so channel config errors do not fail the whole health endpoint
  • retain the Discord startup connected-state regression coverage

Credit

This repair is based on #39921 by FAL1989, with useful ideas and tests carried forward from #42586 by rstar327, #46527 by 0xble, and #52770/#42543 by ajayr.

Validation

  • pnpm -s vitest run src/commands/health.snapshot.test.ts src/gateway/server/health-state.test.ts src/discord/monitor/provider.test.ts
  • pnpm check:changed

Do not close duplicates or superseded PRs until this repair is pushed, /review is clean, and validation passes.

ProjectClownfish replacement details:

Found 0 warnings and 1 error.
Finished in 1.2s on 6 files with 136 rules using 4 threads.

@aisle-research-bot

aisle-research-bot Bot commented Apr 26, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Sensitive data exposure in non-admin health snapshots via unsanitized plugin health summaries
1. 🟠 Sensitive data exposure in non-admin health snapshots via unsanitized plugin health summaries
Property Value
Severity High
CWE CWE-200
Location src/commands/health.ts:394-410

Description

getHealthSnapshot() merges arbitrary plugin-provided buildChannelSummary() objects into the health record even when includeSensitive is false.

  • buildChannelSummary() is typed to return Record<string, unknown> (arbitrary keys).
  • The implementation merges it directly into the response via { ...snapshot, ...summary }.
  • When includeSensitive is false, the code only deletes record.probe.
  • Any other sensitive fields placed by a plugin summary (e.g., token, webhookUrl, channelSecret, stack traces, internal URLs) will be returned to non-admin callers.

Vulnerable code:

const record = summary && typeof summary === "object"
  ? ({ ...snapshot, ...summary } as ChannelAccountHealthSummary)
  : ({ ...snapshot, accountId, configured } satisfies ChannelAccountHealthSummary);
...
if (!includeSensitive) {
  delete record.probe;
}

Because gateway health responses set includeSensitive based on scopes, non-admin clients can still receive these unredacted plugin summary fields.

Recommendation

When includeSensitive === false, enforce an allowlist for fields that may leave the process.

Options:

  1. Project/sanitize plugin summaries before merging (recommended):
const safeSummary = includeSensitive
  ? summary
  : projectSafeChannelAccountSnapshotFields(summary as Record<string, unknown>);

const record = summary && typeof summary === "object"
  ? ({ ...snapshot, ...safeSummary } as ChannelAccountHealthSummary)
  : ({ ...snapshot, accountId, configured } satisfies ChannelAccountHealthSummary);

if (!includeSensitive) {// also defensively drop any known-sensitive keys that might still exist
  delete record.probe;
  delete (record as any).token;
  delete (record as any).secret;
  delete (record as any).webhookUrl;
}
  1. Change the plugin API so buildChannelSummary receives includeSensitive and must return either a safe subset or a { safe, sensitive } structure; only include sensitive for admin callers.

Additionally, consider preventing plugin summaries from overwriting core snapshot fields (e.g., merge as { ...summary, ...snapshot } or explicitly set critical fields after merge).


Analyzed PR: #72417 at commit f9f55bb

Last updated on: 2026-04-27T17:52:03Z

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime commands Command implementations size: M maintainer Maintainer-authored PR labels Apr 26, 2026
@greptile-apps

greptile-apps Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes gateway health snapshots to preserve live runtime-backed channel/account state (connected, lastConnectedAt) by injecting a runtimeSnapshot into getHealthSnapshot, threading a runtime-aware refreshHealthSnapshot wrapper through the WS connection layer, and guarding snapshot capture against channel config errors.

  • P1 – runtime fields dropped via else branch: When buildChannelSummary returns a falsy value (or is absent), the record is rebuilt as { accountId, configured, probe, lastProbeAt } without spreading snapshot, silently discarding whatever runtime fields were captured — see src/commands/health.ts lines 391–399.

Confidence Score: 3/5

Not safe to merge as-is: the else branch in health.ts drops runtime fields for any plugin whose summary is falsy, defeating part of the fix's stated goal.

One P1 finding: the no-summary fallback branch in getHealthSnapshot reconstructs the record without ...snapshot, silently discarding runtime fields from the runtimeSnapshot for plugins that either omit buildChannelSummary or return a non-object from it. The rest of the change — DI wiring, coalescing, error guard, and new tests — is well-structured.

src/commands/health.ts — the else branch in the record assignment around line 391

Comments Outside Diff (1)

  1. src/commands/health.ts, line 391-399 (link)

    P1 Runtime fields dropped when buildChannelSummary returns falsy

    The { ...snapshot, ...summary } path correctly preserves runtime fields (connected, lastConnectedAt) when a plugin returns an object summary. But the else branch reconstructs the record from scratch — { accountId, configured, probe, lastProbeAt } — discarding whatever runtime state was spread into snapshot. If any plugin's buildChannelSummary returns null/undefined/falsy (or if a future plugin omits it entirely), the runtime fields are silently dropped even though they were captured moments earlier.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/commands/health.ts
    Line: 391-399
    
    Comment:
    **Runtime fields dropped when `buildChannelSummary` returns falsy**
    
    The `{ ...snapshot, ...summary }` path correctly preserves runtime fields (`connected`, `lastConnectedAt`) when a plugin returns an object summary. But the else branch reconstructs the record from scratch — `{ accountId, configured, probe, lastProbeAt }` — discarding whatever runtime state was spread into `snapshot`. If any plugin's `buildChannelSummary` returns `null`/`undefined`/falsy (or if a future plugin omits it entirely), the runtime fields are silently dropped even though they were captured moments earlier.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/health.ts
Line: 391-399

Comment:
**Runtime fields dropped when `buildChannelSummary` returns falsy**

The `{ ...snapshot, ...summary }` path correctly preserves runtime fields (`connected`, `lastConnectedAt`) when a plugin returns an object summary. But the else branch reconstructs the record from scratch — `{ accountId, configured, probe, lastProbeAt }` — discarding whatever runtime state was spread into `snapshot`. If any plugin's `buildChannelSummary` returns `null`/`undefined`/falsy (or if a future plugin omits it entirely), the runtime fields are silently dropped even though they were captured moments earlier.

```suggestion
      const record =
        summary && typeof summary === "object"
          ? ({ ...snapshot, ...summary } as ChannelAccountHealthSummary)
          : ({ ...snapshot, accountId, configured, probe, lastProbeAt } as ChannelAccountHealthSummary);
```

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

Reviews (1): Last reviewed commit: "fix(clownfish): address review for ghcra..." | Re-trigger Greptile

@vincentkoc vincentkoc added the clawsweeper Tracked by ClawSweeper automation label Apr 27, 2026
@vincentkoc
vincentkoc force-pushed the clownfish/ghcrawl-207035-agentic-merge branch from 2397a25 to f9f55bb Compare April 27, 2026 17:49
@vincentkoc

Copy link
Copy Markdown
Member Author

ProjectClownfish follow-up pushed: f9f55bb01e

What changed:

  • rebased onto current main
  • addressed the Aisle health disclosure findings by projecting runtime snapshots through the safe channel-account field allowlist
  • kept raw probe payloads off non-sensitive gateway health snapshots
  • prevented sensitive/admin health refreshes from being cached or broadcast
  • kept sensitive and public in-flight refreshes separated so a public caller cannot receive an admin snapshot during a concurrent refresh
  • fixed the stale post-connect health test harness after main moved

Blacksmith validation:

  • pnpm test:serial src/commands/health.snapshot.test.ts src/gateway/server/health-state.test.ts src/channels/account-snapshot-fields.test.ts src/gateway/server/ws-connection.test.ts src/gateway/server/ws-connection/message-handler.post-connect-health.test.ts
    • passed: 13 tests across unit-fast, gateway, and commands shards
  • pnpm check:changed
    • passed: conflict markers, changelog attribution, plugin-sdk re-export guard, core typecheck, core test typecheck, core lint, import cycles, webhook body guard, pairing store guard, pairing account guard

@vincentkoc
vincentkoc merged commit be6263d into main Apr 27, 2026
68 checks passed
@vincentkoc
vincentkoc deleted the clownfish/ghcrawl-207035-agentic-merge branch April 27, 2026 18:05
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
* fix(gateway): preserve runtime-backed health state

* fix(clownfish): address review for ghcrawl-207035-agentic-merge (1)

* fix(gateway): harden health snapshot exposure
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper Tracked by ClawSweeper automation commands Command implementations gateway Gateway runtime maintainer Maintainer-authored PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant