Skip to content

fix(cli): make Matrix recovery paths tolerate stale plugin config d...#53180

Merged
steipete merged 3 commits intoopenclaw:mainfrom
lupuletic:fix/make-matrix-recovery-paths-tolerate-52899
Mar 24, 2026
Merged

fix(cli): make Matrix recovery paths tolerate stale plugin config d...#53180
steipete merged 3 commits intoopenclaw:mainfrom
lupuletic:fix/make-matrix-recovery-paths-tolerate-52899

Conversation

@lupuletic
Copy link
Copy Markdown
Contributor

@lupuletic lupuletic commented Mar 23, 2026

Matrix upgrades can leave config in a state that blocks reinstall/repair, and plugin runtime version reporting can disagree with the actual OpenClaw version.

Closes #52899

Changes:

  • Change plugin runtime version exposure to use the shared version resolver instead of reading package.json directly so plugin compatibility checks report the same runtime version as the CLI/update path.
  • Allow repair-oriented plugin install flow to proceed from an invalid config snapshot for plugins install, using best-effort config instead of hard-failing in preflight.
  • During doctor repair and/or Matrix plugin reinstall, remove stale Matrix plugin load-path/install references left by the old bundled-plugin layout so the rewritten config becomes valid again.
  • Add regression coverage for invalid-config Matrix reinstall, doctor cleanup of stale Matrix plugin refs, and runtime version exposure used by plugin compatibility checks.

Testing:

  • pnpm build && pnpm check && pnpm test
  • Add targeted Vitest coverage around src/cli/plugins-cli.test.ts, src/cli/program/config-guard.test.ts, src/commands/doctor-config-flow.test.ts, and src/plugins/runtime/index.test.ts, then run those scoped tests plus pnpm build to verify the runtime/version boundary still compiles cleanly.

AI-assisted (Claude + Codex committee consensus, fully tested).


AI-Assisted PR Checklist

  • Marked as AI-assisted
  • Testing degree: fully tested (pnpm build + check + test gates passed)
  • Code reviewed by LLM committee (Claude Opus + Codex dual-model review with consensus gate)
  • I understand what the code does
  • Bot review conversations addressed and resolved

…claw#52899)

1. Narrow loadConfigForInstall() to catch only INVALID_CONFIG errors,
   letting real failures (fs permission, OOM) propagate.
2. Assert allow array is properly cleaned in stale-cleanup test.
3. Add comment clarifying version-resolution is already addressed via
   the shared VERSION constant.
4. Run cleanStaleMatrixPluginConfig() during install so
   persistPluginInstall() → writeConfigFile() does not fail validation
   on stale Matrix load paths.
@openclaw-barnacle openclaw-barnacle bot added cli CLI command changes commands Command implementations size: M labels Mar 23, 2026
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 23, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🔵 Low Prototype pollution via object spread in removePluginFromConfig when processing user-controlled config
2 🔵 Low Terminal escape/log injection via untrusted filesystem path in doctor notes

1. 🔵 Prototype pollution via object spread in removePluginFromConfig when processing user-controlled config

Property Value
Severity Low
CWE CWE-1321
Location src/plugins/uninstall.ts:77-161

Description

removePluginFromConfig() constructs new config objects using object spread over values derived from the on-disk config file (JSON5). If a malicious config contains a __proto__ key (or similar special keys), spreading it into a new object can mutate the prototype of the created object, leading to prototype pollution.

This is now more reachable because the new Matrix “stale config cleanup” path calls removePluginFromConfig(cfg,"matrix") during doctor/install flows, which can execute on invalid-but-parseable configs.

Vulnerable code (object spread over untrusted object):

const pluginsConfig = cfg.plugins ?? {};
...
const newPlugins = {
  ...pluginsConfig,
  entries,
  installs,
  allow,
  load,
  slots,
};
...
const config: OpenClawConfig = {
  ...cfg,
  plugins: Object.keys(cleanedPlugins).length > 0 ? cleanedPlugins : undefined,
};

Impact depends on how polluted objects are later used, but typical risks include:

  • Unexpected property lookups succeeding via polluted prototype (e.g., if (obj.admin) style checks)
  • Crashes/DoS due to surprising inherited properties
  • Bypassing logic that assumes plain objects

Recommendation

Avoid spreading/assigning untrusted objects into plain {} targets. Instead, create “plain” objects with a null prototype and copy only expected keys.

Example fix:

function pickPluginsConfig(input: unknown) {
  const out: Record<string, unknown> = Object.create(null);
  if (!input || typeof input !== "object") return out;
  const obj = input as Record<string, unknown>;// Copy only known-safe keys
  for (const key of ["entries", "installs", "allow", "load", "slots"]) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) out[key] = obj[key];
  }
  return out as any;
}

const pluginsConfig = pickPluginsConfig(cfg.plugins);
const newPlugins = Object.assign(Object.create(null), pluginsConfig, {
  entries,
  installs,
  allow,
  load,
  slots,
});

const config = Object.assign(Object.create(null), cfg, {
  plugins: Object.keys(cleanedPlugins).length > 0 ? cleanedPlugins : undefined,
});

Additionally, consider rejecting configs containing __proto__, constructor, or prototype keys during parsing/validation.


2. 🔵 Terminal escape/log injection via untrusted filesystem path in doctor notes

Property Value
Severity Low
CWE CWE-117
Location src/commands/doctor/providers/matrix.ts:106-112

Description

cleanStaleMatrixPluginConfig() builds a human-readable change message that includes issue.path, which ultimately comes from cfg.plugins.installs.matrix (config-controlled) via detectPluginInstallPathIssue().

  • Input (attacker-controlled): install.sourcePath / install.installPath from config file
  • Propagation: detectPluginInstallPathIssue() returns path: candidatePath without sanitization
  • Sink: message is printed to the terminal using note() / @​clack/prompts with no escaping/neutralization of control characters

An attacker who can influence the config (or a synced/shared config) could embed control characters (e.g., ANSI escape sequences, newlines, carriage returns) in the path value. When openclaw doctor runs, the output could be manipulated to:

  • Spoof additional log lines / alter the appearance of warnings (log forging)
  • Hide/overwrite terminal output (ANSI injection)
  • Phish the operator by rendering deceptive prompts/commands

Vulnerable code:

changes: [
  `Removed stale Matrix plugin references (${removed.join(", ")}). ` +
    `The previous install path no longer exists: ${issue.path}`,
],

and the note sink:

clackNote(wrapNoteMessage(message), stylePromptTitle(title));

No output neutralization is performed to remove control characters from interpolated values.

Recommendation

Neutralize control characters before writing untrusted values to terminal/log output.

Options:

  1. Sanitize just the interpolated path (recommended minimal fix):
function sanitizeForTerminal(value: string): string {// Strip C0/C1 control chars and ESC (\u001b) used for ANSI sequences.
  return value.replace(/[\u0000-\u001F\u007F-\u009F\u001B]/g, "");
}// ...
`The previous install path no longer exists: ${sanitizeForTerminal(issue.path)}`
  1. Centralize sanitization in note() so all notes are protected:
export function note(message: string, title?: string) {
  if (isSuppressedByEnv(process.env.OPENCLAW_SUPPRESS_NOTES)) return;
  const safe = message.replace(/[\u0000-\u001F\u007F-\u009F\u001B]/g, "");
  clackNote(wrapNoteMessage(safe), stylePromptTitle(title));
}

Also consider shortening or redacting home directories (e.g., ~/...) if doctor output is commonly shared in issue reports to reduce accidental path disclosure.


Analyzed PR: #53180 at commit b91b344

Last updated on: 2026-03-23T22:39:09Z

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b91b344a38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +186 to +190
} catch (err) {
if (extractErrorCode(err) !== "INVALID_CONFIG") {
throw err;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict fallback recovery to Matrix stale-path invalid configs

loadConfigForInstall now swallows every INVALID_CONFIG error and proceeds, which means plugins install can run download/copy installers even when the config is invalid for unrelated reasons (for example schema mismatches outside Matrix). In that case the command only fails later during writeConfigFile validation, after filesystem side effects have already happened, leaving partially installed plugin directories from a failed install attempt. Please gate this fallback to the specific stale-Matrix-path case (or revalidate before running installers).

Useful? React with 👍 / 👎.

if (!issue || issue.kind !== "missing-path") {
return { config: cfg, changes: [] };
}
const { config, actions } = removePluginFromConfig(cfg, "matrix");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove stale Matrix load paths when sourcePath is missing

This cleanup delegates to removePluginFromConfig, but that helper only removes plugins.load.paths via install.sourcePath; if a stale source: "path" install record has only installPath (which the type allows), detectPluginInstallPathIssue will still report missing-path while the stale load path survives. The resulting config remains invalid, so the intended reinstall/doctor recovery path can still fail to persist.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR fixes two related Matrix upgrade recovery issues: stale plugin install records that block config validation, and plugin runtime version reporting that could disagree with the CLI's own version string.

Key changes:

  • src/plugins/runtime/index.ts: Replaces the createRequire/package.json-based resolveVersion() with the shared VERSION constant, eliminating API-version drift between plugin compatibility checks and the CLI.
  • src/cli/plugins-install-command.ts: New loadConfigForInstall() catches INVALID_CONFIG errors from loadConfig(), falls back to the raw config snapshot, and guards against empty-parse scenarios to avoid overwriting user config with an empty stub. Stale Matrix refs are cleaned as a side-effect of any successful install.
  • src/cli/program/config-guard.ts: plugins install is added to ALLOWED_INVALID_PLUGINS_SUBCOMMANDS so the config-guard preflight no longer exits early when the config is invalid.
  • src/commands/doctor/providers/matrix.ts: New cleanStaleMatrixPluginConfig() detects a missing-path install issue and delegates removal to removePluginFromConfig, correctly clearing the install record, load path, plugin entry, and allowlist entry.
  • src/commands/doctor-config-flow.ts: Runs the stale Matrix cleanup in the doctor flow, following the established applyDoctorConfigMutation pattern.
  • Test coverage is thorough: edge cases for empty-parse fallback, non-config errors, happy-path cleanup, and the new VERSION exposure are all covered.

Confidence Score: 5/5

  • Safe to merge; changes are well-tested and follow established codebase patterns with no correctness issues.
  • All three problem areas (version drift, invalid-config block on install, stale load-path refs) are addressed with targeted, well-scoped changes. Error-handling boundaries are correctly defined (only INVALID_CONFIG is swallowed; fs/OOM errors still surface). The snapshot parse-emptiness guard prevents the most dangerous data-loss scenario ([Bug]: Matrix Plugin API Version Mismatch After Upgrade to v2026.3.22 #52899 concern 4). Test coverage is comprehensive. The one flagged P2 is a minor UX awkwardness (duplicate diagnostic notes in doctor flow) that does not affect correctness or reliability.
  • No files require special attention. The minor UX concern in src/commands/doctor-config-flow.ts (duplicate stale-path diagnostics) is non-blocking.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/doctor-config-flow.ts
Line: 93-102

Comment:
**Duplicate install-path diagnostics for stale Matrix config**

When the Matrix install path is stale/missing, users running `openclaw doctor` will see two separate messages about the same underlying issue:

1. A warning emitted by `collectMatrixInstallPathWarnings` (called inside `runMatrixDoctorSequence` above) — e.g. _"Matrix plugin path is missing. Run `openclaw plugins install @openclaw/matrix`…"_
2. The "Doctor changes: Removed stale Matrix plugin references…" note emitted here.

Both messages are accurate and complementary (the warning explains the problem; this note says what `--fix` will clean up), so there's no correctness issue. But a first-time user may be puzzled seeing a "you need to reinstall" warning immediately followed by a "stale refs removed" change note.

A low-friction option is to pass the stale-cleanup result back into `runMatrixDoctorSequence` (or run the check before it) so `collectMatrixInstallPathWarnings` can skip the install-path warning for the `missing-path` case when cleanup is already pending. Alternatively, a small prose note alongside the Doctor-changes output clarifying "After cleanup, reinstall with `openclaw plugins install @openclaw/matrix`" would remove the ambiguity.

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

Reviews (1): Last reviewed commit: "fix(plugins): address review feedback fo..." | Re-trigger Greptile

Comment on lines +93 to +102
const staleMatrixCleanup = await cleanStaleMatrixPluginConfig(candidate);
if (staleMatrixCleanup.changes.length > 0) {
note(staleMatrixCleanup.changes.join("\n"), "Doctor changes");
({ cfg, candidate, pendingChanges, fixHints } = applyDoctorConfigMutation({
state: { cfg, candidate, pendingChanges, fixHints },
mutation: staleMatrixCleanup,
shouldRepair,
fixHint: `Run "${doctorFixCommand}" to remove stale Matrix plugin references.`,
}));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Duplicate install-path diagnostics for stale Matrix config

When the Matrix install path is stale/missing, users running openclaw doctor will see two separate messages about the same underlying issue:

  1. A warning emitted by collectMatrixInstallPathWarnings (called inside runMatrixDoctorSequence above) — e.g. "Matrix plugin path is missing. Run openclaw plugins install @openclaw/matrix…"
  2. The "Doctor changes: Removed stale Matrix plugin references…" note emitted here.

Both messages are accurate and complementary (the warning explains the problem; this note says what --fix will clean up), so there's no correctness issue. But a first-time user may be puzzled seeing a "you need to reinstall" warning immediately followed by a "stale refs removed" change note.

A low-friction option is to pass the stale-cleanup result back into runMatrixDoctorSequence (or run the check before it) so collectMatrixInstallPathWarnings can skip the install-path warning for the missing-path case when cleanup is already pending. Alternatively, a small prose note alongside the Doctor-changes output clarifying "After cleanup, reinstall with openclaw plugins install @openclaw/matrix" would remove the ambiguity.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/doctor-config-flow.ts
Line: 93-102

Comment:
**Duplicate install-path diagnostics for stale Matrix config**

When the Matrix install path is stale/missing, users running `openclaw doctor` will see two separate messages about the same underlying issue:

1. A warning emitted by `collectMatrixInstallPathWarnings` (called inside `runMatrixDoctorSequence` above) — e.g. _"Matrix plugin path is missing. Run `openclaw plugins install @openclaw/matrix`…"_
2. The "Doctor changes: Removed stale Matrix plugin references…" note emitted here.

Both messages are accurate and complementary (the warning explains the problem; this note says what `--fix` will clean up), so there's no correctness issue. But a first-time user may be puzzled seeing a "you need to reinstall" warning immediately followed by a "stale refs removed" change note.

A low-friction option is to pass the stale-cleanup result back into `runMatrixDoctorSequence` (or run the check before it) so `collectMatrixInstallPathWarnings` can skip the install-path warning for the `missing-path` case when cleanup is already pending. Alternatively, a small prose note alongside the Doctor-changes output clarifying "After cleanup, reinstall with `openclaw plugins install @openclaw/matrix`" would remove the ambiguity.

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

@steipete steipete merged commit cbb11b3 into openclaw:main Mar 24, 2026
35 of 39 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm check && pnpm build and OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test
  • Land commit: fb5ce5e
  • Merge commit: cbb11b3

Thanks @lupuletic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes commands Command implementations size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Matrix Plugin API Version Mismatch After Upgrade to v2026.3.22

2 participants