Release: add plugin npm publish workflow#47678
Conversation
Greptile SummaryThis PR introduces a two-phase GitHub Actions workflow for publishing Key changes:
Confidence Score: 4/5
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecb092c567
ℹ️ 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".
ecb092c to
8b1779f
Compare
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟠 Unpinned GitHub Actions references (tag-based) allow supply-chain compromise
DescriptionThe workflow uses third-party GitHub Actions by mutable tags rather than immutable commit SHAs. If an upstream action tag is moved or a maintainer account is compromised, the workflow could execute attacker-controlled code in CI, potentially leading to:
Vulnerable locations (examples):
Vulnerable code: - name: Checkout
uses: actions/checkout@v6RecommendationPin all third-party actions to an immutable commit SHA (and optionally keep the version comment for readability). Example: - name: Checkout
uses: actions/checkout@<COMMIT_SHA> # v4.2.2Also review indirect action usage via local composite actions (e.g., 2. 🔵 Git option/argument injection via unvalidated --base-ref/--head-ref passed to
|
| Property | Value |
|---|---|
| Severity | Low |
| CWE | CWE-88 |
| Location | scripts/lib/plugin-npm-release.ts:309-317 |
Description
The plugin NPM release tooling accepts --base-ref and --head-ref from CLI args without validation, and passes them directly as positional arguments to git diff.
Because git diff parses options until it reaches a non-option argument, a ref value that starts with - (e.g., --no-index, --output=..., --patch) can be interpreted as a git diff option rather than a revision. This allows an attacker who can control these arguments to change the behavior of the git invocation (option injection), potentially causing:
- incorrect change detection (release plan/check using the wrong set of changed extensions)
- unexpected filesystem reads/writes depending on the injected
git diffoption (e.g.,--no-indexchanges semantics to comparing paths;--output=<file>may write output to a file) - potential information exposure in logs if output mode is changed
Relevant locations:
- argument source:
parsePluginReleaseArgsallows arbitrary--base-ref/--head-refstrings with no validation [scripts/lib/plugin-npm-release.ts:94-150] - sink:
execFileSync('git', ['diff', ... baseRef, headRef, '--', 'extensions'])[scripts/lib/plugin-npm-release.ts:298-323]
Vulnerable code:
execFileSync(
"git",
["diff", "--name-only", "--diff-filter=ACMR", baseRef, headRef, "--", "extensions"],
{ cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
);Note: In the provided GitHub workflow, BASE_REF/HEAD_REF appear to be commit SHAs, which reduces exploitability in CI; however, the library/API itself remains vulnerable if invoked with attacker-controlled refs (now or in future automation).
Recommendation
Harden git ref handling to prevent git diff option injection.
Option A (recommended): resolve and validate refs to commit SHAs
- Reject values that begin with
-or contain whitespace/control characters. - Resolve to a commit SHA using
git rev-parse --verifyand pass the resolved SHA togit diff.
Example:
function resolveCommitSha(rootDir: string, ref: string): string {
if (!ref || /^-/.test(ref) || /\s/.test(ref)) {
throw new Error(`Invalid git ref: ${JSON.stringify(ref)}`);
}
return execFileSync(
"git",
["rev-parse", "--verify", `${ref}^{commit}`],
{ cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
).trim();
}
const baseSha = resolveCommitSha(rootDir, baseRef);
const headSha = resolveCommitSha(rootDir, headRef);
const changedPaths = execFileSync(
"git",
["diff", "--name-only", "--diff-filter=ACMR", baseSha, headSha, "--", "extensions"],
{ cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
).trim().split("\n").filter(Boolean);Option B: add an explicit end-of-options marker before revisions (and keep a second -- for pathspec), if compatible with your git diff invocation:
["diff", "--name-only", "--diff-filter=ACMR", "--", baseRef, headRef, "--", "extensions"]Either approach ensures refs cannot be interpreted as options.
3. 🔵 GitHub Actions npm publish runs package lifecycle scripts with OIDC token permission
| Property | Value |
|---|---|
| Severity | Low |
| CWE | CWE-250 |
| Location | scripts/plugin-npm-publish.sh:18-25 |
Description
The plugin NPM release workflow publishes packages using npm publish without disabling lifecycle scripts.
scripts/plugin-npm-publish.shconstructs and executesnpm publish ... --provenancewithout--ignore-scripts([scripts/plugin-npm-publish.sh:18-25,42-45]).- The publishing job grants
id-token: write([.github/workflows/plugin-npm-release.yml:174-182]) and then runs the publish script ([.github/workflows/plugin-npm-release.yml:213-214]). - If any published plugin package (e.g., under
extensions/*) contains lifecycle scripts such asprepublishOnly,prepare,prepack, etc.,npm publishmay execute them during publish. - With
id-token: write, those scripts can access GitHub Actions OIDC environment variables and attempt to mint/exfiltrate an OIDC token or use it to publish other packages configured for trusted publishing.
Vulnerable code:
publish_cmd=(npm publish --access public --provenance)
...
cd "${package_dir}"
"${publish_cmd[@]}"Note: the current publishable plugin package.json files in this diff do not define a scripts section, but this workflow design remains brittle and becomes exploitable if scripts are added (intentionally or maliciously) to any publishable plugin package.
Recommendation
Disable lifecycle scripts during publishing, and run any required build steps explicitly.
Option A (recommended): add --ignore-scripts to publish:
publish_cmd=(npm publish --access public --provenance --ignore-scripts)
# and for beta:
publish_cmd=(npm publish --access public --tag beta --provenance --ignore-scripts)Option B: enforce via env var at the workflow step:
- name: Publish
env:
npm_config_ignore_scripts: "true"
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"Additionally:
- Add a CI check to ensure publishable plugin packages do not include lifecycle scripts (fail if
package.json.scriptscontainspre*publish*,prepare,prepack, etc.). - Keep the
npm-releaseenvironment protected (required reviewers) and restrict who can runworkflow_dispatchpublishes.
Analyzed PR: #47678 at commit 2a46c1f
Last updated on: 2026-03-18T13:18:07Z
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a46c1f700
ℹ️ 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".
| async function main() { | ||
| checkPluginVersions(); | ||
| checkAppcastSparkleVersions(); | ||
| await checkPluginSdkExports(); |
There was a problem hiding this comment.
Restore lockstep checks for installable npm plugins
If a plugin is published independently after this change, older OpenClaw releases will still install that newer package because channel setup passes bare @openclaw/... specs from src/commands/channel-setup/plugin-install.ts:185-191, and installPluginFromNpmSpec in src/plugins/install.ts:707-728 only validates the npm spec format before fetching it. There is no host/plugin compatibility gate anywhere in that path, so removing the checkPluginVersions() call here means a plugin can start using newer openclaw/plugin-sdk exports and then fail to load for stable/beta users on older core versions.
Useful? React with 👍 / 👎.
* main: (230 commits) fix llm-task invalid thinking timeout Build: narrow tsdown unresolved import guard Plugins: sync contract registry image providers Build: fail on unresolved tsdown imports Deps: align pi-agent-core for declaration builds Build: fail on plugin SDK declaration errors Release: add plugin npm publish workflow (openclaw#47678) fix(security): block build-tool and glibc env injection vectors in host exec sandbox (openclaw#49702) test simplify zero-state boundary guards ci enforce boundary guardrails test: enable vmForks for targeted channel test runs test(telegram): fix incomplete sticker-cache mocks in tests Config: align model compat thinking format types Tlon: pin api-beta to current known-good commit Plugin SDK: harden provider auth seams fix(config): add missing qwen-chat-template to thinking format schema Plugin SDK: register provider auth login entrypoint Plugin SDK: split provider auth login seam fix: serialize duplicate channel starts (openclaw#49583) (thanks @sudie-codes) Telegram: fix reply-runtime test typings ...
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit
* Release: add plugin npm publish workflow * Release: make plugin publish scope explicit (cherry picked from commit d41c9ad)
* fix(ci): stop serializing push workflow runs (cherry picked from commit 0a20c5c) * test: harden path resolution test helpers (cherry picked from commit 1ad47b8) * Fix launcher startup regressions (openclaw#48501) * Fix launcher startup regressions * Fix CI follow-up regressions * Fix review follow-ups * Fix workflow audit shell inputs * Handle require resolve gaxios misses (cherry picked from commit 313e5bb) * refactor(scripts): move container setup entrypoints (cherry picked from commit 46ccbac) * perf(ci): gate install smoke on changed-smoke (openclaw#52458) (cherry picked from commit 4bd90f2) * Docs: prototype generated plugin SDK reference (openclaw#51877) * Chore: unblock synced main checks * Docs: add plugin SDK docs implementation plan * Docs: scaffold plugin SDK reference phase 1 * Docs: mark plugin SDK reference surfaces unstable * Docs: prototype generated plugin SDK reference * docs(plugin-sdk): replace generated reference with api baseline * docs(plugin-sdk): drop generated reference plan * docs(plugin-sdk): align api baseline flow with config docs --------- Co-authored-by: Onur <[email protected]> Co-authored-by: Vincent Koc <[email protected]> (cherry picked from commit 4f1e12a) * fix(ci): harden docker builds and unblock config docs (cherry picked from commit 9f08af1) * Docs: add config drift baseline statefile (openclaw#45891) * Docs: add config drift statefile generator * Docs: generate config drift baseline * CI: move config docs drift runner into workflow sanity * Docs: emit config drift baseline json * Docs: commit config drift baseline json * Docs: wire config baseline into release checks * Config: fix baseline drift walker coverage * Docs: regenerate config drift baselines (cherry picked from commit cbec476) * Release: add plugin npm publish workflow (openclaw#47678) * Release: add plugin npm publish workflow * Release: make plugin publish scope explicit (cherry picked from commit d41c9ad) * build: default to Node 24 and keep Node 22 compat (cherry picked from commit deada7e) * ci(android): use explicit flavor debug tasks (cherry picked from commit 0c2e6fe) * ci: harden pnpm sticky cache on PRs (cherry picked from commit 29b36f8) * CI: add built plugin singleton smoke (openclaw#48710) (cherry picked from commit 5a2a4ab) * chore: add code owners for npm release paths (cherry picked from commit 5c9fae5) * test add extension plugin sdk boundary guards (cherry picked from commit 77fb258) * ci: tighten cache docs and node22 gate (cherry picked from commit 797b6fe) * ci: add npm release workflow and CalVer checks (openclaw#42414) (thanks @onutc) (cherry picked from commit 8ba1b6e) * CI: add CLI startup memory regression check (cherry picked from commit c0e0115) * Add bad-barnacle label to prevent barnacle closures. (openclaw#51945) (cherry picked from commit c449a0a) * ci: speed up scoped workflow lanes (cherry picked from commit d17490f) * ci: restore PR pnpm cache fallback (cherry picked from commit e1d0545) * CI: guard gateway watch against duplicate runtime regressions (openclaw#49048) (cherry picked from commit f036ed2) * fix: correct domain reference in docker setup script * fix: adapt cherry-picks for fork TS strictness * fix: adapt cherry-picked tests for fork structure - Dockerfile test: OPENCLAW_ → REMOTECLAW_ ARG names - ci-changed-scope test: add missing runChangedSmoke field - doc-baseline test: rename to e2e (needs dist/ build artifacts) - extension boundary test: update baselines and expectations for fork * fix: adjust ci-changed-scope test for fork's narrower skills regex --------- Co-authored-by: Vincent Koc <[email protected]> Co-authored-by: Peter Steinberger <[email protected]> Co-authored-by: Tak Hoffman <[email protected]> Co-authored-by: Bob <[email protected]> Co-authored-by: Onur <[email protected]> Co-authored-by: Altay <[email protected]> Co-authored-by: Ayaan Zaidi <[email protected]> Co-authored-by: Onur Solmaz <[email protected]> Co-authored-by: Harold Hunt <[email protected]>
Summary
@openclaw/*plugin npm publishesopenclaw.release.publishToNpmand stop enforcing lockstep plugin versions inrelease:checkmainTesting
pnpm test -- test/plugin-npm-release.test.tspnpm release:plugins:npm:checkpnpm release:plugins:npm:check -- --plugins @openclaw/feishu,@openclaw/zalopnpm release:plugins:npm:plan -- --plugins @openclaw/feishu,@openclaw/zalopnpm release:plugins:npm:check -- --base-ref "$(git rev-parse HEAD~1)" --head-ref "$(git rev-parse HEAD)"pnpm release:plugins:npm:plan -- --base-ref "$(git rev-parse HEAD~1)" --head-ref "$(git rev-parse HEAD)"bash scripts/plugin-npm-publish.sh --dry-run /Users/onur/offline/openclaw/extensions/feishu