Skip to content

fix(plugin-sdk): remove relative extension boundary escapes#51939

Merged
Takhoffman merged 5 commits intomainfrom
test/relative-outside-package-guard
Mar 22, 2026
Merged

fix(plugin-sdk): remove relative extension boundary escapes#51939
Takhoffman merged 5 commits intomainfrom
test/relative-outside-package-guard

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

  • Problem: bundled extension runtime-api.ts barrels and one BlueBubbles helper import still used relative paths that escaped their own package roots, and the relative-outside-package boundary check tolerated the existing violations via a checked-in baseline.
  • Why it matters: the rule only detected drift instead of enforcing the policy, so repo code could keep shipping known boundary escapes.
  • What changed: added the missing public openclaw/plugin-sdk/* subpaths, rewired the violating extension imports to those public seams, and changed relative-outside-package to hard-fail on any violation.
  • What did NOT change (scope boundary): no channel behavior, runtime semantics, or non-boundary refactors beyond the public plugin-sdk export surface needed to remove the escapes.

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 #
  • Related #

User-visible / Behavior Changes

Bundled extensions now consume plugin SDK seams through public openclaw/plugin-sdk/* subpaths instead of relative escapes. The relative-outside-package rule now fails immediately on any future violation.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22, pnpm 10
  • Model/provider: n/a
  • Integration/channel (if any): bundled extension plugin-sdk surfaces
  • Relevant config (redacted): none

Steps

  1. Run pnpm lint:extensions:no-relative-outside-package on main and inspect the non-empty inventory.
  2. Replace each relative escape with a public openclaw/plugin-sdk/* import and export the missing subpaths.
  3. Re-run the boundary lint, targeted tests, and pnpm build.

Expected

  • relative-outside-package reports no violations and fails if any reappear.
  • Public plugin-sdk exports stay in sync with the package export map and build artifacts.

Actual

  • Local validation is green with the violations removed.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: ran pnpm lint:extensions:no-relative-outside-package, pnpm lint:plugins:plugin-sdk-subpaths-exported, pnpm test -- test/extension-plugin-sdk-boundary.test.ts src/plugin-sdk/runtime-api-guardrails.test.ts src/plugin-sdk/subpaths.test.ts, and pnpm build.
  • Edge cases checked: confirmed BlueBubbles now imports the shared iMessage parsing helpers through openclaw/plugin-sdk/imessage-core; confirmed the boundary script no longer accepts a non-empty relative-outside-package baseline.
  • What you did not verify: broader end-to-end channel runtime behavior beyond the contract/build gates above.

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.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

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

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert commit ad5d80862a.
  • Files/config to restore: package.json, scripts/lib/plugin-sdk-entrypoints.json, and the touched extension runtime-api.ts / targets.ts files.
  • Known bad symptoms reviewers should watch for: missing plugin-sdk subpath resolution at runtime, or guardrail test failures if a runtime-api barrel drifts from the curated export list.

Risks and Mitigations

  • Risk: expanding the public plugin-sdk surface exposes more subpaths than before.
    • Mitigation: the new entries are existing curated source files only, covered by plugin-sdk:check-exports, subpaths.test.ts, and the targeted boundary/runtime-api tests.

AI-assisted: Yes. Tested locally.

@openclaw-barnacle openclaw-barnacle bot added channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser scripts Repository scripts channel: feishu Channel integration: feishu channel: twitch Channel integration: twitch channel: irc labels Mar 22, 2026
@vincentkoc vincentkoc self-assigned this Mar 22, 2026
@openclaw-barnacle openclaw-barnacle bot added size: M maintainer Maintainer-authored PR labels Mar 22, 2026
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

4 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@Takhoffman Takhoffman force-pushed the test/relative-outside-package-guard branch from 89973a6 to a3e9bb0 Compare March 22, 2026 00:44
@Takhoffman Takhoffman force-pushed the test/relative-outside-package-guard branch from a3e9bb0 to 181db5b Compare March 22, 2026 00:45
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: 181db5b147

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@Takhoffman Takhoffman reopened this Mar 22, 2026
@Takhoffman Takhoffman merged commit 2b4c3c2 into main Mar 22, 2026
58 checks passed
@Takhoffman Takhoffman deleted the test/relative-outside-package-guard branch March 22, 2026 01:03
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: 50c0130e01

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +15 to +17
"peerDependenciesMeta": {
"openclaw": {
"optional": true
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 Make the new OpenClaw peer dependency mandatory

In the plugin install path we unpack the plugin and run npm install --omit=dev inside that isolated directory (src/infra/install-package-dir.ts:188-199). Because the new openclaw peer is marked optional here, npm accepts the install without checking/materializing the host package, so users on older OpenClaw releases can still install or update these plugins and only discover the break later when the new openclaw/plugin-sdk/* imports from their runtime barrels cannot be resolved. The same peerDependenciesMeta.openclaw.optional stanza was added to the other updated public plugin manifests in this commit, so the compatibility gate is still ineffective.

Useful? React with 👍 / 👎.

// Keep this barrel thin and aligned with the local extension surface.

export * from "../../src/plugin-sdk/twitch.js";
export * from "openclaw/plugin-sdk/twitch";
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 Add a minimum host-version gate for Twitch

extensions/twitch/runtime-api.ts now depends on the brand-new openclaw/plugin-sdk/twitch export, but extensions/twitch/package.json still has no peerDependencies.openclaw floor. Twitch is installable from npm today — the docs explicitly tell users to run openclaw plugins install @openclaw/twitch (docs/channels/twitch.md:16-20), and catalog resolution falls back to the package name when openclaw.install.npmSpec is absent (src/channels/plugins/catalog.ts:187-195) — so hosts older than this commit can accept the package and then fail at load time because their loader never aliases that new subpath.

Useful? React with 👍 / 👎.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 22, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🔵 Low Symlink-following write enables arbitrary file overwrite when sanitizing bundled extension package.json

1. 🔵 Symlink-following write enables arbitrary file overwrite when sanitizing bundled extension package.json

Property Value
Severity Low
CWE CWE-59
Location scripts/stage-bundled-plugin-runtime-deps.mjs:80-82

Description

sanitizeBundledManifestForRuntimeInstall(pluginDir) rewrites ${pluginDir}/package.json before running npm install.

Because the code writes the manifest using fs.writeFileSync() without verifying that package.json is a regular file (not a symlink/hardlink), an attacker who can influence the contents of dist/extensions/<plugin>/ (or any caller-provided repoRoot) can cause arbitrary file overwrite by making package.json a symlink to a sensitive path.

  • Input: pluginDir comes from scanning dist/extensions/* (or from a custom repoRoot passed into stageBundledPluginRuntimeDeps())
  • Attack primitive: create dist/extensions/<id>/package.json as a symlink to an arbitrary target (e.g., ~/.ssh/config, CI secrets files, etc.)
  • Sink: fs.writeFileSync(manifestPath, ...) follows the symlink and overwrites the target when changed === true

Vulnerable code:

function writeJson(filePath, value) {
  fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
...
if (changed) {
  writeJson(manifestPath, packageJson);
}

This is a classic symlink attack risk in build/staging scripts when operating on potentially untrusted directories/artifacts.

Recommendation

Harden writes to package.json to reject symlinks (and ideally avoid TOCTOU).

At minimum, fail closed if the manifest is not a regular file:

import fs from "node:fs";
import path from "node:path";

function safeWriteJson(manifestPath, value, pluginDir) {// Ensure the resolved path stays within the plugin directory
  const realPluginDir = fs.realpathSync(pluginDir);
  const realManifestDir = fs.realpathSync(path.dirname(manifestPath));
  if (realManifestDir !== realPluginDir) {
    throw new Error(`Refusing to write manifest outside pluginDir: ${manifestPath}`);
  }

  const st = fs.lstatSync(manifestPath);
  if (st.isSymbolicLink() || !st.isFile()) {
    throw new Error(`Refusing to write non-regular manifest file: ${manifestPath}`);
  }

  fs.writeFileSync(manifestPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}

For stronger protection on platforms that support it, open the file with O_NOFOLLOW (fs.constants.O_NOFOLLOW) and write via the returned fd, or write to a temp file and rename() it into place after verifying the destination is not a symlink.


Analyzed PR: #51939 at commit 50c0130

Last updated on: 2026-03-22T01:47:09Z

JohnJAS pushed a commit to JohnJAS/openclaw that referenced this pull request Mar 22, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
pholpaphankorn pushed a commit to pholpaphankorn/openclaw that referenced this pull request Mar 22, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
MaheshBhushan pushed a commit to MaheshBhushan/openclaw that referenced this pull request Mar 22, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
frankekn pushed a commit to artwalker/openclaw that referenced this pull request Mar 23, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
furaul pushed a commit to furaul/openclaw that referenced this pull request Mar 24, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Mar 28, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
(cherry picked from commit 2b4c3c2)

# Conflicts:
#	scripts/stage-bundled-plugin-runtime-deps.mjs
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Apr 4, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
alexey-pelykh pushed a commit to remoteclaw/remoteclaw that referenced this pull request Apr 4, 2026
…#51939)

* fix(plugin-sdk): remove relative extension boundary escapes

* Gate new plugin-sdk subpaths on host version

* Add changelog entry for openclaw#51939

* Fix local staging for plugin-sdk host version gate

* Raise host floor for line and googlechat plugins

---------

Co-authored-by: Tak Hoffman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bad-barnacle Prevent Barnacle auto-close channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser maintainer Maintainer-authored PR scripts Repository scripts size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants