Skip to content

fix(plugins): localize bundled runtime deps to extensions#67099

Merged
vincentkoc merged 10 commits intomainfrom
pr-65478-security-fix
Apr 15, 2026
Merged

fix(plugins): localize bundled runtime deps to extensions#67099
vincentkoc merged 10 commits intomainfrom
pr-65478-security-fix

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: bundled plugins with stageRuntimeDependencies were still effectively root-owned because root dist/ chunks imported extension runtime deps like @buape/carbon, and staged plugin package scopes broke some plugin-local runtime loading paths.
  • Why it matters: npm install/packaging stayed heavier than necessary, extension dependency ownership was wrong, and bundled plugin runtime isolation was brittle.
  • What changed: staged bundled plugins now build into isolated dist/extensions/<id> graphs, Discord-owned deps moved out of the root package, plugin-local package version lookups were hardened for staged layouts, and the plugin loader now keeps dist/extensions/* modules on the aliased Jiti path so openclaw/plugin-sdk/* still resolves inside nested plugin package scopes.
  • What did NOT change (scope boundary): no user-facing channel behavior changes were intended beyond packaging/runtime ownership correctness; unrelated existing pi-embedded-runner worktree changes were left untouched.
  • AI-assisted: yes. This PR was prepared with a coding agent and manually verified locally.

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

Root Cause (if applicable)

  • Root cause: bundled plugins that staged runtime deps were still sharing the root build graph, so root dist/ emitted extension-owned chunks and root dependency ownership could not be reduced safely. Once plugins were split into isolated staged graphs, two hidden assumptions surfaced: some plugin code assumed ../package.json from src/* would still be valid after bundling, and the native-loader fast path bypassed plugin-sdk alias resolution inside nested dist/extensions/<id>/package.json scopes.
  • Missing detection / guardrail: there was no contract asserting that staged bundled plugin dist modules must still resolve plugin-local manifests and openclaw/plugin-sdk/* imports under nested package scopes.
  • Contributing context (if known): Discord ownership pressure exposed the packaging issue first, but the same staged-runtime assumptions also affected Feishu, QQBot, and any staged plugin relying on plugin-sdk aliasing.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/channels/plugins/contracts/directory.registry-backed.contract.test.ts, src/plugins/contracts/package-manifest.contract.test.ts, src/plugins/sdk-alias.test.ts, test/scripts/root-dependency-ownership-audit.test.ts
  • Scenario the test should lock in: staged bundled plugins load from dist/extensions/<id> without reaching back into root-owned runtime deps, plugin-local package manifests still resolve, and bundled plugin dist modules still resolve openclaw/plugin-sdk/* through the loader alias boundary.
  • Why this is the smallest reliable guardrail: the regression only appears after build output exists and the bundled plugin loader reads staged plugin dist modules through the real runtime boundary.
  • Existing test that already covers this (if any): the directory contract suite and package manifest contract suite now cover the broken runtime and manifest ownership paths directly.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

  • Bundled Discord runtime deps are now extension-owned instead of root-owned.
  • npm pack / staged bundled plugin packaging no longer requires Discord runtime deps to remain mirrored at the root package surface.
  • pnpm build is green again after narrowing OpenAI Responses reasoning-effort typing.

Diagram (if applicable)

Before:
[root dist graph] -> [bundled plugin chunks in root dist] -> [root package must own extension runtime deps]

After:
[root dist graph] -> [core + non-staged plugins]
[staged plugin build] -> [dist/extensions/<id>] -> [plugin-local runtime deps + plugin-sdk alias boundary]

Security Impact (required)

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

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22.20.0 / pnpm 10.32.1
  • Model/provider: N/A
  • Integration/channel (if any): Discord, Slack, Feishu, QQBot, bundled plugin packaging
  • Relevant config (redacted): default local repo config

Steps

  1. Build the repo with staged bundled plugin outputs.
  2. Load bundled channel/plugin contracts from built dist/extensions/<id> artifacts.
  3. Verify root dependency ownership and manifest contracts.

Expected

  • Extension-owned staged runtime deps stay under the owning extension.
  • Bundled plugin dist modules still resolve plugin-local manifests and openclaw/plugin-sdk/* imports.
  • pnpm build passes.

Actual

  • Before this change, root dist/ still imported Discord-owned deps, staged plugin dist paths broke some ../package.json lookups, and native loading of dist/extensions/* bypassed plugin-sdk alias resolution.

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: pnpm build; pnpm test src/plugins/sdk-alias.test.ts; pnpm exec vitest run --config test/vitest/vitest.contracts.config.ts src/channels/plugins/contracts/directory.registry-backed.contract.test.ts --reporter=verbose; pnpm exec vitest run --config test/vitest/vitest.contracts.config.ts src/plugins/contracts/package-manifest.contract.test.ts --reporter=verbose; pnpm test test/scripts/root-dependency-ownership-audit.test.ts; pnpm test test/release-check.test.ts test/openclaw-npm-postpublish-verify.test.ts; pnpm test src/agents/openai-transport-stream.test.ts -t 'defaults OpenAI Responses reasoning effort to high when unset'
  • Edge cases checked: staged plugin package version lookup in flattened dist output; plugin-sdk alias resolution under nested dist/extensions/<id>/package.json scopes; root ownership audit after build only still flags hono.
  • What you did not verify: full repo-wide pnpm test; external npm publish/install smoke beyond local packaging/runtime checks.

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/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps:

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk: staged bundled plugin dist modules have a different loader resolution path than other built modules.
    • Mitigation: loader policy is now covered by src/plugins/sdk-alias.test.ts and exercised by the bundled directory contract suite.
  • Risk: plugin-local manifest version lookups can still hide similar relative-path assumptions in other staged plugins.
    • Mitigation: Feishu and QQBot were updated now, and the staged-plugin runtime/contract coverage catches the runtime class of failure.

@vincentkoc vincentkoc self-assigned this Apr 15, 2026
@openclaw-barnacle openclaw-barnacle Bot added channel: line Channel integration: line scripts Repository scripts agents Agent runtime and tooling channel: feishu Channel integration: feishu channel: qqbot extensions: qa-lab size: XL maintainer Maintainer-authored PR labels Apr 15, 2026
@vincentkoc vincentkoc marked this pull request as ready for review April 15, 2026 09:19
@vincentkoc vincentkoc marked this pull request as draft April 15, 2026 09:19
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: 49e3c0b054

ℹ️ 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 thread src/agents/openai-transport-stream.ts
@vincentkoc vincentkoc force-pushed the pr-65478-security-fix branch from 49e3c0b to 2ff3f19 Compare April 15, 2026 09:38
@openclaw-barnacle openclaw-barnacle Bot added size: L and removed agents Agent runtime and tooling extensions: qa-lab size: XL labels Apr 15, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 15, 2026

Greptile Summary

This PR isolates staged bundled plugin builds into dist/extensions/<id> graphs, moves Discord-owned runtime deps out of the root package, hardens plugin-local package.json version lookups with a multi-candidate fallback, and adds a new root-dependency ownership audit tool. The sdk-alias.ts change correctly disables native Jiti loading for dist/extensions/* paths so the openclaw/plugin-sdk/* alias map stays active inside nested plugin scopes.

  • The installPluginRuntimeDeps refactor splits required/optional dep installs correctly, but the !fs.existsSync(stagedNodeModulesDir) guard at line 742 still throws unconditionally — including when requiredSpecs is empty and all optional installs silently failed, contradicting the "optional deps must not block staging" intent.

Confidence Score: 4/5

Safe to merge for current plugins, but has a latent logic bug in the optional-only deps path of the new install refactor.

One P1 finding: the node_modules existence guard fires unconditionally even when only optional specs were attempted and swallowed — contradicting the comment at line 737. No current staged plugin has only optional runtime deps (all have at least one dependencies entry), so the bug is latent today but would manifest if any future plugin or a current plugin update lands in that state.

scripts/stage-bundled-plugin-runtime-deps.mjs lines 741–746

Prompt To Fix All With AI
This is a comment left during a code review.
Path: scripts/stage-bundled-plugin-runtime-deps.mjs
Line: 741-746

Comment:
**Optional-only deps can still block staging**

When a plugin has only `optionalDependencies` (no `dependencies`) and `stageRuntimeDependencies: true`, the required-install block is skipped entirely, the optional install silently swallows failures (line 736–738), and then this check fires — throwing an error even though the intent is that optional deps must not block staging.

```suggestion
    const stagedNodeModulesDir = path.join(tempInstallDir, "node_modules");
    if (requiredSpecs.length > 0 && !fs.existsSync(stagedNodeModulesDir)) {
      throw new Error(
        `failed to stage bundled runtime deps for ${pluginId}: explicit npm install produced no node_modules directory`,
      );
    }
```

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

---

This is a comment left during a code review.
Path: extensions/qqbot/src/slash-commands.ts
Line: 17-43

Comment:
**`readPluginVersion` duplicated within qqbot**

The same `PACKAGE_JSON_CANDIDATES` + `readPluginVersion()` helper is defined independently in both `extensions/qqbot/src/api.ts` and here. Since both files are in the same package, extracting it to a shared local helper (e.g. `utils/plugin-version.ts`) would eliminate the duplication. The same pattern was also necessary in `extensions/feishu/src/client.ts`, but that's in a different package.

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

Reviews (2): Last reviewed commit: "Merge branch 'main' into pr-65478-securi..." | Re-trigger Greptile

@vincentkoc vincentkoc marked this pull request as ready for review April 15, 2026 10:01
Comment thread scripts/stage-bundled-plugin-runtime-deps.mjs
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: 4631cb8117

ℹ️ 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 thread scripts/stage-bundled-plugin-runtime-deps.mjs Outdated
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 15, 2026

🔒 Aisle Security Analysis

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

# Severity Title
1 🟠 High Symlink traversal allows arbitrary filesystem delete/overwrite during runtime deps staging
2 🟠 High Supply-chain exposure: published package omits bundled extension node_modules causing postinstall to fetch & execute unpinned deps
3 🟡 Medium Symlink-based workspace root confusion allows staging runtime deps from outside repository
4 🔵 Low DoS via invalid npm dependency specifiers not rejected in runtime deps pinning
1. 🟠 Symlink traversal allows arbitrary filesystem delete/overwrite during runtime deps staging
Property Value
Severity High
CWE CWE-22
Location scripts/stage-bundled-plugin-runtime-deps.mjs:24-545

Description

The runtime dependency staging script operates on plugin directories discovered under dist/extensions without validating that dist, dist/extensions, or pluginDir are not symlinks/junctions.

  • listBundledPluginRuntimeDirs(repoRoot) trusts repoRoot/dist/extensions and returns each pluginDir path.
  • Later, the script performs recursive deletion and atomic renames within pluginDir (e.g., removing or replacing pluginDir/node_modules and writing .openclaw-runtime-deps-stamp.json).
  • Hardening via assertPathIsNotSymlink() only checks the final target path (e.g., .../node_modules) and does not detect symlinked parent components.

If an attacker can supply a workspace where dist (or dist/extensions) is a symlink to an arbitrary location, the script may delete/overwrite directories outside the repository during build/packaging.

Vulnerable flow:

  • Input: filesystem structure under repoRoot (potentially attacker-controlled checkout)
  • Path construction: pluginDir = path.join(repoRoot, "dist", "extensions", <name>)
  • Sinks: fs.rmSync(..., {recursive:true, force:true}), fs.renameSync(...), fs.writeFileSync(...) executed under the symlinked tree

Recommendation

Harden path handling by ensuring the entire directory chain is within the expected repo and contains no symlink/junction components before any destructive operation.

Recommended approach:

  1. Resolve and validate roots:
const distRoot = path.join(repoRoot, "dist");
const extensionsRoot = path.join(distRoot, "extensions");
const realExtensionsRoot = fs.realpathSync(extensionsRoot);
const realRepoRoot = fs.realpathSync(repoRoot);
if (!realExtensionsRoot.startsWith(realRepoRoot + path.sep)) {
  throw new Error(`extensionsRoot escapes repo via symlink: ${extensionsRoot}`);
}
  1. For each pluginDir, validate no symlinks in any component (walk from extensionsRoot to pluginDir and lstatSync each segment), or use a dedicated utility that enforces “no symlinks anywhere in path”.

  2. Perform destructive operations only on validated real paths, e.g.:

const realPluginDir = fs.realpathSync(pluginDir);
if (!realPluginDir.startsWith(realExtensionsRoot + path.sep)) throw new Error("...");

This prevents a symlinked dist/extensions from redirecting deletes/renames outside the repository.

2. 🟠 Supply-chain exposure: published package omits bundled extension node_modules causing postinstall to fetch & execute unpinned deps
Property Value
Severity High
CWE CWE-494
Location package.json:23-44

Description

The published openclaw npm package now excludes dist/extensions/*/node_modules/** from its files list. This means bundled extensions may ship without their runtime dependencies.

On install, scripts/postinstall-bundled-plugins.mjs detects missing runtime deps and runs npm install to fetch them dynamically. This increases supply-chain risk:

  • Dependency versions come from dist/extensions/*/package.json and may be semver ranges (e.g., ^1.2.3), so the install may pull newer, unreviewed packages over time.
  • The nested npm install invocation does not pass --ignore-scripts, so arbitrary lifecycle scripts from downloaded packages will execute during openclaw's postinstall.
  • This turns an install of openclaw into a networked, script-executing dependency fetch, expanding the attack surface (e.g., registry compromise, typosquatting, dependency confusion in custom registries).

Relevant changed packaging line:

"files": [
  ...,
  "!dist/extensions/*/node_modules/**",
  ...
]

Postinstall sink (for context):

npmArgs: ["install", "--omit=dev", "--no-save", "--package-lock=false", "--legacy-peer-deps", ...missingSpecs]

Recommendation

Avoid dynamic network installs during end-user postinstall.

Options:

  1. Ship the needed runtime deps with the package (preferred):
  • Remove the exclusion and keep dist/extensions/*/node_modules/** in the published tarball.
  1. If you must stage at install time, harden the installer:
  • Use a precomputed, exact-version lock/manifest generated at build time.
  • Ensure installs do not execute lifecycle scripts.
  • Consider vendoring from the root package's already-installed dependency graph rather than fetching.

Example hardening (in scripts/postinstall-bundled-plugins.mjs):

npmArgs: [
  "install",
  "--omit=dev",
  "--no-save",
  "--package-lock=false",
  "--ignore-scripts",
  "--silent",
  ...missingSpecsPinnedToExactVersions
]

Also validate that missingSpecs are exact versions only (no ranges, no URLs, no file/link/workspace specs), similar to the checks added in scripts/stage-bundled-plugin-runtime-deps.mjs.

3. 🟡 Symlink-based workspace root confusion allows staging runtime deps from outside repository
Property Value
Severity Medium
CWE CWE-59
Location scripts/stage-bundled-plugin-runtime-deps.mjs:547-570

Description

resolveInstalledWorkspacePluginRoot() derives an installedWorkspaceRoot from realpathSync(repoRoot/node_modules) and then uses that to locate extensions/${pluginId}.

If repoRoot/node_modules is a symlink (or otherwise resolves outside the checkout), this logic can:

  • Select a directDependencyPackageRoot outside repoRoot
  • Prefer dependency roots under ${directDependencyPackageRoot}/node_modules when resolving direct deps
  • Cause stageInstalledRootRuntimeDeps() / dependency pinning to read and copy dependency trees from an unintended external workspace, potentially attacker-controlled, into the bundled output.

Vulnerable code:

installedWorkspaceRoot = path.dirname(fs.realpathSync(nodeModulesDir));
...
const installedPluginRoot = path.join(installedWorkspaceRoot, "extensions", pluginId);
if (fs.existsSync(path.join(installedPluginRoot, "package.json"))) {
  return installedPluginRoot;
}

Recommendation

Constrain the resolved workspace root to the current repository and/or refuse symlinked node_modules when deriving trust boundaries.

Suggested hardening:

const nodeModulesDir = path.join(repoRoot, "node_modules");// Refuse symlinked node_modules at the repo root.
if (fs.existsSync(nodeModulesDir) && fs.lstatSync(nodeModulesDir).isSymbolicLink()) {
  return currentPluginRoot;
}

const realNodeModules = fs.realpathSync(nodeModulesDir);
const installedWorkspaceRoot = path.dirname(realNodeModules);
const realRepoRoot = fs.realpathSync(repoRoot);// Ensure installedWorkspaceRoot is the repoRoot (or within it, if desired)
if (installedWorkspaceRoot !== realRepoRoot) {
  return currentPluginRoot;
}

Additionally, when using directDependencyPackageRoot, validate it is inside the trusted workspace before preferring its nested node_modules during dependency resolution.

4. 🔵 DoS via invalid npm dependency specifiers not rejected in runtime deps pinning
Property Value
Severity Low
CWE CWE-20
Location scripts/stage-bundled-plugin-runtime-deps.mjs:611-646

Description

The fallback runtime dependency pinning logic allows certain npm dependency specifiers that are not semver ranges (e.g., npm alias npm:pkg@​1.2.3, git/GitHub shorthands like github:user/repo, dist-tag like latest). These pass isSafeRuntimeDependencySpec() but later get used as the spec argument to semverSatisfies(), which will throw on invalid ranges.

  • Input: packageJson.dependencies|optionalDependencies values (dependency version/spec strings)
  • Validation gap: isSafeRuntimeDependencySpec() only blocks a few URL/protocol/path forms; it does not restrict specs to valid semver ranges.
  • Sink: dependencyVersionSatisfied() calls semverSatisfies(installedVersion, spec, ...) without a try/catch.
  • Impact: A crafted dependency spec can crash stageBundledPluginRuntimeDeps / collectRuntimeDependencyInstallSpecs, causing build or packaging failure (denial of service).

Vulnerable code paths:

function isSafeRuntimeDependencySpec(spec) {// ... does not reject `npm:`, `github:`, `latest`, etc.
}

function dependencyVersionSatisfied(spec, installedVersion) {
  return semverSatisfies(installedVersion, spec, { includePrerelease: false });
}

Recommendation

Treat non-semver specs as invalid before calling semverSatisfies, and explicitly block npm alias/VCS shorthand/tag formats.

Suggested hardening:

import semverValidRange from "semver/ranges/valid.js";

function assertSafeRuntimeDependencySpec(depName, spec) {
  if (!isSafeRuntimeDependencySpec(spec)) {
    throw new Error(`disallowed runtime dependency spec for ${depName}: ${spec}`);
  }// Reject npm alias / non-semver specifiers
  if (spec.startsWith("npm:")) {
    throw new Error(`disallowed npm alias spec for ${depName}: ${spec}`);
  }
  if (!semverValidRange(spec)) {
    throw new Error(`runtime dependency spec must be a semver range for ${depName}: ${spec}`);
  }
}

function dependencyVersionSatisfied(spec, installedVersion) {
  try {
    return semverSatisfies(installedVersion, spec, { includePrerelease: false });
  } catch {
    return false;
  }
}

This prevents unexpected exceptions and ensures the pinning logic only operates on semver ranges it can interpret safely.


Analyzed PR: #67099 at commit ac7b3b2

Last updated on: 2026-04-15T10:57:28Z

@vincentkoc vincentkoc requested a review from a team as a code owner April 15, 2026 10:41
@openclaw-barnacle openclaw-barnacle Bot added the docker Docker and sandbox tooling label Apr 15, 2026
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: bc4d0b8ac8

ℹ️ 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 +713 to +715
optionalDependencies: resolvePinnedRuntimeDependencyGroup(
runtimeGroups.optionalDependencies,
params,
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 Skip unresolved optional runtime deps when pinning

This now routes optionalDependencies through the same exact-version resolver as required deps, so staging fails whenever an optional package is not present in the root install graph. In practice, optional native deps (for example @discordjs/opus) are often skipped on unsupported hosts or when native build prerequisites are missing; with this code path, stageBundledPluginRuntimeDeps throws and aborts runtime-postbuild instead of proceeding without that optional package.

Useful? React with 👍 / 👎.

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: ac7b3b2880

ℹ️ 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 +14 to +18
const IMPORT_PATTERNS = [
/\bfrom\s*["']([^"']+)["']/g,
/\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
/\brequire\s*\(\s*["']([^"']+)["']\s*\)/g,
/\b(?:require|[_$A-Za-z][\w$]*require[\w$]*)\.resolve\s*\(\s*["']([^"']+)["']\s*\)/gi,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match side-effect imports in dependency ownership scan

collectModuleSpecifiers only matches from, dynamic import(...), and require(...), so bare side-effect imports like import "pkg"; are never counted. This makes collectRootDependencyOwnershipAudit underreport real usage and can misclassify required root deps as unreferenced/localizable, which is risky because this script is used to drive dependency-removal decisions. Add a static bare-import pattern (as used in scripts/lib/bundled-plugin-root-runtime-mirrors.mjs) so these imports are included.

Useful? React with 👍 / 👎.

@vincentkoc vincentkoc merged commit c727388 into main Apr 15, 2026
45 of 50 checks passed
@vincentkoc vincentkoc deleted the pr-65478-security-fix branch April 15, 2026 11:04
xudaiyanzi pushed a commit to xudaiyanzi/openclaw that referenced this pull request Apr 17, 2026
…7099)

* fix(plugins): localize bundled runtime deps to extensions

* fix(plugins): move staged runtime deps out of root

* fix(packaging): harden prepack and runtime dep staging

* fix(packaging): preserve optional runtime dep staging

* Update CHANGELOG.md

* fix(packaging): harden runtime staging filesystem writes

* fix(docker): ship preinstall warning in bootstrap layers

* fix(packaging): exclude staged plugin node_modules from npm pack
kvnkho pushed a commit to kvnkho/openclaw that referenced this pull request Apr 17, 2026
…7099)

* fix(plugins): localize bundled runtime deps to extensions

* fix(plugins): move staged runtime deps out of root

* fix(packaging): harden prepack and runtime dep staging

* fix(packaging): preserve optional runtime dep staging

* Update CHANGELOG.md

* fix(packaging): harden runtime staging filesystem writes

* fix(docker): ship preinstall warning in bootstrap layers

* fix(packaging): exclude staged plugin node_modules from npm pack
iT2afL0rd pushed a commit to iT2afL0rd/openclaw that referenced this pull request Apr 22, 2026
…tall

When `openclaw update` runs `openclaw doctor` after replacing the package
files, bundled plugin runtime deps (grammy etc.) haven't been installed yet —
`ensureBundledPluginRuntimeDeps` only runs later during plugin loader
initialisation.

The static `import * as grammy from 'grammy'` at the top of allowed-updates.ts
is pulled in transitively before the installer has a chance to run:

  doctor → channel-doctor → bundled channel registry
  → telegram/channel.ts → telegram/monitor.ts
  → telegram/allowed-updates.ts  ← static ESM import grammy  💥

The fix replaces the top-level import with a lazy `createRequire()` call so
grammy is only resolved when `resolveTelegramAllowedUpdates()` / the module
constant is first evaluated, by which point the installer has already placed
grammy under dist/extensions/telegram/node_modules/.  Fallback constants
(already present) ensure correct behaviour when grammy is genuinely absent.

Reproduces with:
  rm -rf dist/extensions/telegram/node_modules
  openclaw doctor --non-interactive
  # → Error: Cannot find module 'grammy'

Fixes the regression introduced by openclaw#67099 (localize bundled plugin runtime deps).
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…7099)

* fix(plugins): localize bundled runtime deps to extensions

* fix(plugins): move staged runtime deps out of root

* fix(packaging): harden prepack and runtime dep staging

* fix(packaging): preserve optional runtime dep staging

* Update CHANGELOG.md

* fix(packaging): harden runtime staging filesystem writes

* fix(docker): ship preinstall warning in bootstrap layers

* fix(packaging): exclude staged plugin node_modules from npm pack
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…7099)

* fix(plugins): localize bundled runtime deps to extensions

* fix(plugins): move staged runtime deps out of root

* fix(packaging): harden prepack and runtime dep staging

* fix(packaging): preserve optional runtime dep staging

* Update CHANGELOG.md

* fix(packaging): harden runtime staging filesystem writes

* fix(docker): ship preinstall warning in bootstrap layers

* fix(packaging): exclude staged plugin node_modules from npm pack
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…7099)

* fix(plugins): localize bundled runtime deps to extensions

* fix(plugins): move staged runtime deps out of root

* fix(packaging): harden prepack and runtime dep staging

* fix(packaging): preserve optional runtime dep staging

* Update CHANGELOG.md

* fix(packaging): harden runtime staging filesystem writes

* fix(docker): ship preinstall warning in bootstrap layers

* fix(packaging): exclude staged plugin node_modules from npm pack
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: feishu Channel integration: feishu channel: line Channel integration: line channel: qqbot docker Docker and sandbox tooling maintainer Maintainer-authored PR scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant