news 2026/10/12 3:03:30

gemini-watermark-remover 的 npm Trusted Publishing 配置:基于 GitHub Actions OIDC 的无 Token 发布实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gemini-watermark-remover 的 npm Trusted Publishing 配置:基于 GitHub Actions OIDC 的无 Token 发布实战

【免费下载链接】gemini-watermark-remover

A high-performance, 100% client-side tool for removing Gemini AI image & video watermarks. Built with pure JavaScript using mathematically precise Reverse Alpha Blending. / 基于 JavaScript 的纯浏览器端 Gemini AI 图像和视频无损去水印工具,使用数学精确的反向 Alpha 混合算法

项目地址:https://gitcode.com/gh_mirrors/ge/gemini-watermark-remover
点击查看免费下载

本文以@pilio/gemini-watermark-remover开源仓库为实例,讲解如何通过 npmjs.com 的Trusted Publisher能力,让 GitHub Actions 利用 OIDC(OpenID Connect)身份直接发布 npm 包,从而彻底告别长期NPM_TOKEN。读完本文,你将掌握 Trusted Publisher 的一次性配置步骤、publish-npm.yml工作流的源码级校验逻辑、手动重跑发布的全部前置条件,以及将发布访问升级为「2FA + 禁用 Token」的安全加固流程,可直接迁移到自己的 npm 仓库。

为什么需要 Trusted Publishing:不保存长期NPM_TOKEN

传统做法是在 GitHub Secrets 中保存一个长期有效的 npm automation token(NPM_TOKEN),工作流运行时再把它注入npm publish。这种做法有两个明显的短板:

  • 凭证泄露面大:长期 token 一旦被 fork、被恶意 PR 或日志泄露,攻击者可以长期冒用发布身份;
  • 缺少发布来源证明:npm 无法自动证明「这个包是由仓库 A 的提交 B 构建发布的」。

Trusted Publishing 的思路与此相反:npmjs.com 信任 GitHub 的 OIDC 身份提供方,GitHub Actions 在每次运行时通过id-token: write权限动态申请一个短期、绑定本次 workflow run 的身份令牌,npm 据此完成认证,仓库内不再保存任何长期NPM_TOKEN。由于每次发布的身份都是临时签发的,即使凭证泄露也会在运行结束后自然失效;同时 npm 会自动为该次发布生成provenance(发布来源证明),将包与具体的仓库、工作流和提交绑定。

在@pilio/gemini-watermark-remover仓库中,这一方案已经落地:发布工作流 .github/workflows/publish-npm.yml 的顶层permissions只声明了两个权限,见该文件 L9-L11:

permissions: contents: read id-token: write

contents: read只允许读取仓库内容,id-token: write专用于申请 OIDC 令牌,其余权限一律未授予——这是整个发布链路能够不依赖NPM_TOKEN的基石。

npmjs.com 一次性配置:将 GitHub Actions 注册为 Trusted Publisher

该步骤只需要在 npmjs.com 上配置一次,之后所有版本都由工作流自动发布。具体操作如下:

  1. 登录 npmjs.com,打开@pilio/gemini-watermark-remover的package settings;
  2. 在Trusted Publisher区域选择GitHub Actions;
  3. 按下表填写各项:
配置项填写值说明
Organization or userGargantuaX拥有目标仓库的 GitHub 组织或用户名,与本仓库 package.json 中author字段及repository归属一致
Repositorygemini-watermark-remover发布来源仓库名
Workflow filenamepublish-npm.yml必须与仓库中 .github/workflows/publish-npm.yml 的文件名完全一致,npm 只信任该工作流签发的 OIDC 身份
Environment name留空该仓库未使用 GitHub Actions 的 deployment environment,因此留空;如果工作流被限制在某个 environment 中,此处需填写对应的 environment 名
Allowed actions只选择npm publish只允许npm publish这一个 action,最大程度缩小可执行操作面

保存后,先完成一次 OIDC 发布并确认成功,再把Publishing access改为Require two-factor authentication and disallow tokens,最后撤销所有不再使用的 npm automation token(具体见下文「安全加固」小节)。

发布工作流源码解析:从 Release 到 npm Registry

配置好 Trusted Publisher 后,日常发布完全由工作流自动完成。打开 .github/workflows/publish-npm.yml 可以看到它的完整结构。

触发方式:Release 发布 + 手动触发

on: release: types: - published workflow_dispatch:
  • 当维护者创建并发布一个 GitHub Release(release: published)时,工作流自动运行;
  • workflow_dispatch允许在 GitHub Actions 界面手动运行Publish npm package,用于 GitHub Release 已经存在、但需要重新发布 npm 包的场景(例如首次迁移 Trusted Publishing 时补齐发布记录)。

并发控制与运行环境

concurrency: group: npm-publish cancel-in-progress: false jobs: publish: runs-on: ubuntu-latest

发布使用 GitHub-hosted runner(ubuntu-latest),并通过concurrency.group: npm-publish保证同一时间只允许一个发布任务运行;cancel-in-progress: false意味着后来的任务会排队等待而非取消正在进行的发布,避免并发发布互相覆盖导致 registry 状态错乱。

环境准备三步

- name: Checkout release source uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup pnpm uses: pnpm/action-setup@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 registry-url: https://registry.npmjs.org package-manager-cache: false

关键点在于actions/checkout@v6的fetch-depth: 0:发布校验需要访问 git tag 指向的历史对象(计算 tag 中 tarball 的 SHA-256、判断 tag 是否为当前提交祖先),因此必须拉取完整历史而非浅克隆。Node.js 版本固定为 24,与 package.json 中packageManager: pnpm@10.11.0声明的 pnpm 版本配合使用;registry-url指向 npm 官方 registry,Trusted Publishing 的 OIDC 认证正是通过 setup-node 写入的 npmrc 生效。

核心校验步骤:发布前逐条验证版本化.tgz

工作流中名为Validate versioned release package(L37-L80)的步骤是整个发布流程的灵魂。它以set -euo pipefail严格模式执行以下校验链:

package_name="$(node --input-type=module -e "import manifest from './package.json' with { type: 'json' }; process.stdout.write(manifest.name)")" version="$(node --input-type=module -e "import manifest from './package.json' with { type: 'json' }; process.stdout.write(manifest.version)")" tag="v${version}" tarball="release/pilio-gemini-watermark-remover-${version}.tgz"

先读取当前package.json中的包名与版本,推导出预期的 git tag(v<version>)与 tarball 路径(release/pilio-gemini-watermark-remover-<version>.tgz),然后依次执行五重校验:

  1. Release tag 与包版本一致:如果本次由 Release 事件触发,github.event.release.tag_name必须等于v${version},否则直接失败;
  2. v<version>标签存在且是当前提交的祖先:通过git rev-parse --verify refs/tags/${tag}^{commit}确认标签对象存在,再用git merge-base --is-ancestor "${tag}" HEAD确认该标签在提交历史中早于或等于当前 checkout 的提交——这保证发布的正是被标签锚定的那份代码;
  3. 版本化.tgz已提交:test -f "${tarball}"确认release/下确实存在与版本对应的 tarball 文件;
  4. 当前.tgz与该标签中的文件 SHA-256 完全一致:
tag_tarball_sha="$(git show "${tag}:${tarball}" | sha256sum | cut -d ' ' -f 1)" checkout_tarball_sha="$(sha256sum "${tarball}" | cut -d ' ' -f 1)" if [[ "${tag_tarball_sha}" != "${checkout_tarball_sha}" ]]; then echo "The checked-out tarball differs from the artifact committed at ${tag}." >&2 exit 1 fi

用git show读出标签指向提交中 tarball 的字节流并计算 SHA-256,与工作区实际文件的 SHA-256 比对。这保证了「发布的内容 = 打标签时审阅过的内容」,杜绝了标签之后对release/*.tgz的篡改被静默发布; 5..tgz内的包名和版本与仓库package.json一致:用tar -xOf直接解出 tarball 内部的package/package.json,在一个内联 Node 脚本中校验packed.name与packed.version是否分别等于仓库package.json的name与version,防止「仓库版本号已更新但 tarball 还是旧包」的错配发布。

校验通过后,步骤把package_name、version、tag、tarball写入GITHUB_OUTPUT,供后续步骤消费。

幂等发布:版本已存在时安全跳过

发布前还有一个专门步骤Check whether version is already published(L82-L96):

if published_version="$(pnpm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version 2>/dev/null)" && [[ "${published_version}" == "${PACKAGE_VERSION}" ]]; then echo "already_published=true" >> "${GITHUB_OUTPUT}" echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published; nothing to do." else echo "already_published=false" >> "${GITHUB_OUTPUT}" fi

它通过pnpm view查询 npm registry,若目标版本已经存在则标记already_published=true,真正的发布步骤会跳过:

- name: Publish with npm Trusted Publishing if: steps.registry.outputs.already_published != 'true' run: pnpm publish "./${{ steps.release.outputs.tarball }}" --access public --no-git-checks

最终发布命令是对版本化 tarball 执行pnpm publish:

  • --access public:以公开 scope 发布(@pilio为公开包,见 package.json);
  • --no-git-checks:跳过 pnpm 默认的 git 状态检查,因为发布源是已经过严格校验的标签化 tarball,而不是当前工作树;
  • 没有注入任何NPM_TOKEN:认证完全由 setup-node 配置的 registry + OIDC 身份完成,npm 会为本次发布自动生成 provenance。

注意--no-git-checks并不意味着降低安全标准——恰恰相反,它只有在前面「五重校验」全部通过的前提下才会被执行。仓库测试 tests/project/publishNpmWorkflow.test.js 专门验证了这一点:该测试构造一个 detached tag checkout(模拟发布现场),用正则从工作流中提取真实的 publish 命令,并对本地 pack 出的 tarball 执行pnpm publish --dry-run,确保「从标签化 tarball 发布」这条命令路径在任何 git 状态(包括 detached HEAD)下都能正确工作。

手动重跑发布:GitHub Release 已存在时的workflow_dispatch

如果 GitHub Release 已经存在(例如仓库从旧的NPM_TOKEN方案首次迁移到 Trusted Publishing 时,需要补发当前版本),可以在 GitHub Actions 页面手动运行Publish npm package。手动任务同样执行上面完整的五重校验,因此必须同时满足:

  • v<version>标签存在,且是当前提交的祖先;
  • 版本化.tgz(release/pilio-gemini-watermark-remover-<version>.tgz)已提交到仓库;
  • 当前 checkout 的.tgz与该标签中的文件 SHA-256 完全一致;
  • .tgz内的包名和版本与仓库package.json一致。

其中「标签是当前提交祖先」这一条在手动触发时尤为重要:如果直接对某个没有 tag 锚定的最新提交手动发布,第二步校验会失败,从而防止发布未经 Release 流程审阅的内容。由于发布是幂等的,即使该版本已经存在于 npm,任务也会安全跳过,不会产生错误覆盖。

安全加固:发布后收紧访问与撤销 Token

Trusted Publishing 配置完成并通过一次实际发布验证后,应立即执行以下加固(原文档「保存后先完成一次 OIDC 发布并确认成功,再把 Publishing access 改为 Require two-factor authentication and disallow tokens,最后撤销不再使用的 npm automation token」):

  1. 完成一次 OIDC 发布并确认成功:先让 Trusted Publisher 链路实际跑通一次,避免尚未验证配置就锁死发布通道;
  2. 将 Publishing access 改为Require two-factor authentication and disallow tokens:此后 npm 账号下所有长期 token 一律失效,只有通过 OIDC 的 Trusted Publishing 路径可以发布,把攻击面收敛到「只有publish-npm.yml这个被 npm 明确信任的工作流」;
  3. 撤销不再使用的 npm automation token:包括此前用于旧发布流程的NPM_TOKEN,以及任何其他遗留 automation token,确保仓库 Secrets 中不再存在可长期使用的发布凭证。

结合工作流 L9-L11 的最小权限声明,这套方案形成三层防护:临时 OIDC 身份(分钟级有效期)、仅允许npm publish的 Trusted Publisher 配置、账号级禁用 token + 强制 2FA。

发布后的验证:release:distribution-check与 npm 面确认

发布不是终点。仓库通过 scripts/check-release-distribution.js 提供发布分布检查(对应pnpm release:distribution-check),它会同时核对多个发布面,其中与 npm 相关的是:

  • npm registry 检查:请求https://registry.npmjs.org/@pilio%2Fgemini-watermark-remover,取dist-tags.latest,与package.json中的期望版本比对(对应源码中npmUrl与npmLatest的checkVersion('npm-latest', ...)逻辑);
  • GitHub Release 资产检查:期望v<version>的 release assets 中必须包含pilio-gemini-watermark-remover-<version>.tgz,与发布工作流消费的 tarball 命名规则完全一致;
  • 其余检查项还包括官网 userscript、Chrome 扩展 fallback 包与 SHA-256、latest-extension.json等发布面。

RELEASE.md 的 Post-Release 清单也明确要求:确认pnpm release:distribution-check对 local package、GitHub Release、release assets、npm latest 等面均输出ok,并以生成的报告作为发布面状态的最终依据。这些是检验「OIDC 发布是否真正生效」的客观证据。

最佳实践小结

回顾整个@pilio/gemini-watermark-remover的 Trusted Publishing 落地,可以提炼出几条可直接复用的经验:

  1. 工作流文件名即信任边界:npmjs.com 上的Workflow filename必须与仓库中的 .github/workflows/publish-npm.yml 完全一致,改名或迁移仓库后需要在 npm 侧同步更新;
  2. 校验交给工作流,而不是人的自觉:版本对齐、标签祖先、tarball SHA-256、包内 manifest 一致性这四类校验全部脚本化,发布前任何一环失败都会让任务整体失败,配合 tests/project/publishNpmWorkflow.test.js 的回归测试,保证校验逻辑本身不被意外破坏;
  3. 发布产物先于发布存在:版本化.tgz必须预先构建并提交到release/目录,publish-npm.yml只是对它做验证与上传,这与 .github/workflows/ci.yml 中先pnpm pack打包 release candidate、再由发布流程消费的思路一致;
  4. 权限最小化 + 账号级禁用 token:工作流只保留contents: read与id-token: write,npm 侧再叠加「仅允许npm publish」与「2FA + disallow tokens」,让 OIDC 短期凭证成为唯一的发布钥匙;
  5. 发布后立即验证:用pnpm release:distribution-check核对 npmdist-tags.latest与 GitHub Release 资产,把「发布成功」落实为可审计的证据,而不是只看 CI 任务绿了。

这套配置同样适用于任何托管在 GitHub、以 npm 包形式分发的开源项目:只需在 npmjs.com 注册 Trusted Publisher、提交一份带五重校验的publish-npm.yml,即可在不保存任何长期 token 的前提下,获得自动 provenance 的合规发布链路。

【免费下载链接】gemini-watermark-remover

A high-performance, 100% client-side tool for removing Gemini AI image & video watermarks. Built with pure JavaScript using mathematically precise Reverse Alpha Blending. / 基于 JavaScript 的纯浏览器端 Gemini AI 图像和视频无损去水印工具,使用数学精确的反向 Alpha 混合算法

项目地址:https://gitcode.com/gh_mirrors/ge/gemini-watermark-remover
点击查看免费下载

相关推荐

上一篇:CANN Range算子实现
下一篇:MPack终极指南:C语言中最高效的MessagePack序列化库

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 3:03:08

UniApp跨端开发:Vue2与Vue3无限滚动加载方案与避坑指南

做UniApp跨端开发&#xff0c;十有八九会撞到同一个需求&#xff1a;列表无限滚动加载。尤其在小程序端&#xff0c;用户的习惯已经被头部产品训练成"刷不到底"的信息流&#xff0c;如果做成传统的上一页/下一页按钮&#xff0c;体验上总觉得差了那么一口气。我自己在…

作者头像 李华
网站建设 2026/10/12 3:01:34

Flyway执行无限等待排查实战:从锁竞争到连接池的完整链路

如果你见过这种场景&#xff1a;服务启动后&#xff0c;日志停在某一行&#xff0c;类似“Migrating schema to version 12 ...”&#xff0c;然后整个进程就像被按了暂停键&#xff0c;日志不再滚动&#xff0c;接口不响应&#xff0c;重启一次还是这样&#xff0c;那就已经掉…

作者头像 李华
网站建设 2026/10/12 2:59:22

pocketsphinx.js:纯前端离线语音识别方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 2:59:05

PyTorch强化学习动态路径规划:43页实战文档拆解与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/12 2:58:19

PLC五种编程语言详解:梯形图、结构化文本怎么选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华