openclaw - ✅(Solved) Fix [Bug]: QQBot credential backups bypass gateway state isolation [1 pull requests, 1 comments, 2 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#84313Fetched 2026-05-20 03:41:34
View on GitHub
Comments
1
Participants
2
Timeline
10
Reactions
1
Author
Timeline (top)
labeled ×8commented ×1cross-referenced ×1

Root Cause

openclaw/SECURITY.md says "For advanced setups, multiple gateways on one machine are possible, but only with strict isolation" and "For untrusted-user isolation, split by trust boundary: separate gateways and separate OS users/hosts per boundary." This finding breaks the documented separate-gateway isolation layer because QQBot persists credentials in a home-global path instead of the active OPENCLAW_STATE_DIR. It is not the out-of-scope shared-gateway case because the repro uses two different gateway instances with different configs and state directories, matching docs/gateway/index.md:157-185 and docs/gateway/multiple-gateways.md:119-127.

Impact

A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.

Fix Action

Fixed

PR fix notes

PR #84314: fix: QQBot credential backups bypass gateway state isolation

Description (problem / solution / changelog)

Summary

  • Problem: A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.
  • Why it matters: The fix is focused, covers the actual exploit path, and includes a regression test delta.
  • What changed: The fix is focused, covers the actual exploit path, and includes a regression test delta.
  • What did NOT change (scope boundary): No unrelated defaults, migrations, or compatibility behavior were intentionally changed.

Motivation

  • The fix is focused, covers the actual exploit path, and includes a regression test delta.

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 #84313
  • Related #
  • This PR fixes a bug or regression

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.
  • Real environment tested: See Repro + Verification.
  • Exact steps or command run after this patch: node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check
  • Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output): node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check reported passed.
  • Observed result after fix: passed
  • What was not tested: Interactive/manual scenarios not captured in the staged issue bundle.
  • Before evidence (optional but encouraged): See the linked issue analysis.

Root Cause (if applicable)

  • Root cause: The fix is focused, covers the actual exploit path, and includes a regression test delta.
  • 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 node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check
  • Target test or file: Not explicitly recorded in the issue bundle.
  • Scenario the test should lock in: A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.
  • 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): No
  • Command/tool execution surface changed? (Yes/No): Yes
  • Data access scope changed? (Yes/No): Yes
  • If any Yes, explain risk + mitigation: The fix is focused, covers the actual exploit path, and includes a regression test delta.. Mitigation: node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check 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 node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check.

Expected

  • A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.
  • Validation passes for the touched behavior.

Actual

  • Validation status: passed

Evidence

  • Validation evidence: node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check reported passed.
  • CVSS v3.1: 8.4 (High)
  • CVSS v4.0: 9.2 (Critical)
  • Changed files:
  • extensions/qqbot/src/engine/utils/data-paths.test.ts (+49/-0)
  • extensions/qqbot/src/engine/utils/data-paths.ts (+12/-3)

Human Verification (required)

  • Verified scenarios: A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.
  • Edge cases checked: node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check 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: node scripts/run-oxlint.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' 'extensions/qqbot/src/engine/utils/data-paths.ts' && node scripts/run-vitest.mjs 'extensions/qqbot/src/engine/utils/data-paths.test.ts' && pnpm build && pnpm check reported passed and the changed-file scope stayed limited to the recorded diff.

Changed files

  • extensions/qqbot/src/engine/utils/data-paths.test.ts (added, +49/-0)
  • extensions/qqbot/src/engine/utils/data-paths.ts (modified, +12/-3)

Code Example

export function getQQBotDataPath(...subPaths: string[]): string {
  return path.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
}

export function getCredentialBackupFile(accountId: string): string {
  return path.join(getQQBotDataPath("data"), `credential-backup-${safeName(accountId)}.json`);
}

if (account.appId && account.clientSecret) {
  saveCredentialBackup(account.accountId, account.appId, account.clientSecret);
}

if (!account.appId || !account.clientSecret) {
  const backup = loadCredentialBackup(account.accountId);
  if (backup?.appId && backup?.clientSecret) {
    const nextCfg = applyQQBotAccountConfig(cfg, account.accountId, {
      appId: backup.appId,
      clientSecret: backup.clientSecret,
    });
    await writeOpenClawConfigThroughRuntime(getQQBotRuntime(), nextCfg);
  }
}
RAW_BUFFERClick to expand / collapse

Severity Assessment

CVSS Assessment

Metricv3.1v4.0
Score8.4 / 10.09.2 / 10.0
SeverityHighCritical
VectorCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:NCVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N
CalculatorCVSS v3.1 CalculatorCVSS v4.0 Calculator

Threat Model Alignment

Classification: security-specific

openclaw/SECURITY.md says "For advanced setups, multiple gateways on one machine are possible, but only with strict isolation" and "For untrusted-user isolation, split by trust boundary: separate gateways and separate OS users/hosts per boundary." This finding breaks the documented separate-gateway isolation layer because QQBot persists credentials in a home-global path instead of the active OPENCLAW_STATE_DIR. It is not the out-of-scope shared-gateway case because the repro uses two different gateway instances with different configs and state directories, matching docs/gateway/index.md:157-185 and docs/gateway/multiple-gateways.md:119-127.

Impact

A QQBot account that reaches READY on Gateway A writes a reusable credential backup into ~/.openclaw/qqbot/data/. Gateway B, started under the same OS account but a different OPENCLAW_STATE_DIR, can silently import that backup and overwrite its own config with Gateway A's appId and clientSecret, collapsing two supposedly isolated gateway profiles into one shared QQBot identity.

Affected Component

File: extensions/qqbot/src/engine/utils/platform.ts:42-45, extensions/qqbot/src/engine/utils/data-paths.ts:31-37, extensions/qqbot/src/channel.ts:168-170,282-295

export function getQQBotDataPath(...subPaths: string[]): string {
  return path.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
}

export function getCredentialBackupFile(accountId: string): string {
  return path.join(getQQBotDataPath("data"), `credential-backup-${safeName(accountId)}.json`);
}

if (account.appId && account.clientSecret) {
  saveCredentialBackup(account.accountId, account.appId, account.clientSecret);
}

if (!account.appId || !account.clientSecret) {
  const backup = loadCredentialBackup(account.accountId);
  if (backup?.appId && backup?.clientSecret) {
    const nextCfg = applyQQBotAccountConfig(cfg, account.accountId, {
      appId: backup.appId,
      clientSecret: backup.clientSecret,
    });
    await writeOpenClawConfigThroughRuntime(getQQBotRuntime(), nextCfg);
  }
}

Technical Reproduction

  1. Start Gateway A and Gateway B under the same $HOME but with different OPENCLAW_CONFIG_PATH and OPENCLAW_STATE_DIR values, following the multi-gateway isolation guidance in docs/gateway/index.md:157-185.
  2. Configure QQBot on Gateway A with account id default (or any shared account id), let the channel reach READY, and observe persistAccountCredentialSnapshot() call saveCredentialBackup().
  3. Because getCredentialBackupFile() resolves through getQQBotDataPath(), Gateway A writes credential-backup-default.json under ~/.openclaw/qqbot/data/ instead of under Gateway A's state directory.
  4. Start Gateway B with the same QQBot account id but missing appId or clientSecret. During qqbotPlugin.gateway.startAccount(), loadCredentialBackup(account.accountId) reads Gateway A's shared backup and writeOpenClawConfigThroughRuntime() injects those credentials into Gateway B's config.
  5. Observe that Gateway B now connects with Gateway A's QQBot credentials even though both gateways use different state directories and were expected to stay isolated.

Demonstrated Impact

The exploit path is deterministic and does not require modifying trusted local files ahead of time: ordinary QQBot startup on Gateway A creates the credential backup, and ordinary QQBot startup on Gateway B consumes it. The root cause is architectural coupling to os.homedir()/~/.openclaw/qqbot rather than the active gateway state resolver. Existing controls do not prevent the cross-profile import because the restore path runs before the channel starts, explicitly rewrites Gateway B's config with the recovered secret, and keys the backup only by accountId. That lets one isolated gateway profile inherit another profile's bot identity and message authority, violating the documented per-instance config/state split.

Environment

Inspected in openclaw/ at upstream/main commit 3d96111a5afe377b529c9bb5a9db510d74607344. The vulnerable storage and restore paths are implemented in extensions/qqbot/src/engine/utils/platform.ts, extensions/qqbot/src/engine/utils/data-paths.ts, and extensions/qqbot/src/channel.ts. The expected isolation model is documented in docs/gateway/index.md:157-185, docs/gateway/multiple-gateways.md:119-127, and openclaw/SECURITY.md:136-139,236.

Remediation Advice

Route all QQBot durable state, especially credential backups, through the active OpenClaw state-directory resolver and include the gateway profile/state directory in the backup scope so one gateway instance cannot restore another instance's QQBot credentials.

<!-- submission-marker:AR-zst-qqbot-credential-backups-bypass-gateway-state-isolation -->

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]: QQBot credential backups bypass gateway state isolation [1 pull requests, 1 comments, 2 participants]