Files
Netcatty/docs/plugin-platform/github-install.md
T
陈大猫 968e84ff59 feat(plugins): install published plugin packages from GitHub (#4164)
* feat(plugins): install published plugin packages from GitHub

* fix(plugins): prebundle GitHub parser and reset consumed preview trust

* fix(plugins): localize GitHub install errors and trust review
2026-11-07 07:10:08 +08:00

7.7 KiB

Install from GitHub

Settings > Plugins > Open plugin manager > Install from GitHub installs a published package from a public GitHub repository. Local .ncpkg import and marketplace installation remain available.

Accepted repository inputs are https://github.com/owner/repo, github.com/owner/repo, and owner/repo. HTTP is upgraded to HTTPS. An optional trailing .git and single / are removed. Case is normalized to lowercase, so all these forms share https://github.com/owner/repo and the same host-owned source ID. Credentials, explicit ports, other hosts/schemes, query/fragment, percent escapes, backslashes, traversal, and extra path segments are rejected. Paste a repository, not a branch, subdirectory, script or release asset URL.

Author's release contract

The existing package contract is a .ncpkg ZIP produced by @netcatty/plugin-cli: root netcatty.plugin.json, its declared browser entry and resources, and any other validated package contents. The package validator is the authority for paths, manifest semantics, referenced files, CRCs, archive headers, expansion limits and companion integrity. A Git checkout, source ZIP, package.json, and prepare.mjs are not installable packages.

Existing local preparers remain author-side tools. For example, examples/plugins/database-rfam-readonly/prepare.mjs bundles host.js into browser.js, copies netcatty.plugin.json, and calls buildPluginPackage. The author must run this locally, validate the resulting .ncpkg, and attach it to a GitHub release. Netcatty never executes a downloaded preparer, npm lifecycle script, shell command, build tool or repository source.

There was no existing GitHub release discovery/naming contract on main. This flow defines the narrow delivery contract:

  1. Resolve the exact public repository through GitHub's unauthenticated API.
  2. Read /repos/{owner}/{repo}/releases/latest (GitHub's latest published stable release, not a client-side semantic version search). Draft/prerelease-only repositories are unsupported.
  3. Require a nonempty tag and exactly one uploaded .ncpkg release asset. Its filename may be any ASCII name beginning with an alphanumeric character and otherwise containing letters, digits, ., _, -, at most 200 characters. There is no required repository-name prefix. Multiple assets require local import so the app cannot silently choose the wrong platform/package.
  4. Resolve the tag through /repos/{owner}/{repo}/commits/{tag} to a commit SHA. Download the asset's exact GitHub release URL, bound to that repository/tag.
  5. Check release size, optional GitHub sha256: digest, ZIP structure and the entire existing package validator, runtime admission, compatibility and retained source binding before offering a review.
  6. Show the actual package manifest/version, declared permission/resource bounds, source, tag/commit, asset and computed SHA-256. Require explicit trust acknowledgment. Install disabled through PluginManager and PackageStore; the immutable snapshot must match the reviewed digest and manifest.

The manifest is the package version authority; release tag naming is independent and both are displayed. The tag/commit identify release context, not proof that the binary was reproducibly built from that commit. The SHA-256 pins reviewed bytes; neither it nor GitHub ownership verifies a publisher signature.

No accessible repository, no stable release, no tag commit, missing/ambiguous asset, HTML/XML instead of a package, incompatible/invalid package, size/hash mismatch, access denial, rate limit and offline/HTTP failures all fail closed. There is no clone, auto-build, raw-file or registry fallback. Private repositories, login and tokens are unsupported. GitHub API repository moves require entering the current URL, rather than silently changing repository identity.

Boundaries and persistence

Only the trusted host renderer can invoke prepare/install/discard management IPC. Plugin RPC exposes no GitHub retrieval or arbitrary URL fetch. The backend constructs the API URLs and allows only HTTPS api.github.com; API redirects are rejected. Release downloads allow HTTPS github.com, release-assets.githubusercontent.com, objects.githubusercontent.com with at most three redirects. Each redirect is checked before contacting its host. No cookies, Authorization headers, environment credentials or git configuration are consulted. Redirect signing queries are allowed only on these download hosts; repository inputs never accept queries or credentials.

Metadata is capped at 2 MiB; packages at the CLI's 100 MiB archive limit plus all existing expansion/file limits. The network sequence has a 90 second timeout; standalone bounded transfers default to 30 seconds. Streamed bodies are bounded regardless of Content-Length. Temporary archives use Netcatty's managed temp root and mode-0700 private directories, mode-0600 files. Previews are bound to the requesting renderer, expire after ten minutes, and are consumed on install or removed on cancel/window destruction. At most eight active/pending operations are retained. Installation revalidates the private snapshot and reviewed hash and manifest, then uses the existing atomic publication/recovery transaction.

The existing JSON source binding stores kind: github, a repository-derived source ID, canonical URL, tag, commit and asset ID without a schema migration. Private data ownership remains tied to the source ID. Replacing a local or marketplace installation requires the existing explicit uninstall/private-data reset. Installed details show the repository and pin after reopening/restart. To update, enter the same repository again and review its current release; registry update checks intentionally skip GitHub sources. No automatic GitHub update checks or downgrade policy are introduced.

The existing unsigned security principal still binds package bytes and publisher text, so changed unsigned code does not inherit grants. Normal installation admits browser-only/data-theme packages; advanced packages remain behind the existing developer gate. This feature does not add signing or grant advanced runtime trust. Review installed permission modes before enabling; declared supported permissions retain their existing default Allow/Ask/Deny behavior.

Verification

domain/pluginGitHubRepository.test.cjs checks the exact three input forms and identity equivalence. electron/plugins/githubInstallService.test.cjs exercises the real CLI-produced starter package and actual manager/store/database, plus controlled metadata/transfer failures. The renderer interaction test mounts the real Radix dialog. Existing package/manifest and runtime suites retain the archive abuse/companion/permission boundary checks.

Run the existing Electron smoke harness with this checkout built, the local SSH fixture running, and:

NC_APP="$PWD" NC_OUT=/workspace/netcatty-smoke-shots/github-install/pass \
NC_EXTRA="$PWD/scripts/plugin-github-install.smoke.mjs" \
xvfb-run -a -s '-screen 0 1440x900x24' \
node /workspace/netcatty-smoke-harness/smoke.mjs

The extra uses Playwright on the actual app, a real starter .ncpkg and real loopback HTTP downloads through a main-process-only injected transport. Production URL checks, resolver, validator, preview, IPC, permissions and installation all run unchanged. The repository name/commit in this fixture are explicitly synthetic, not a claim of a public release. It records an MP4 of the display and actual Electron screenshots, rejects invalid input and a missing release, reviews permissions, installs disabled, changes a permission mode and reopens the manager to verify persistence. It never enables or executes the fixture plugin, and requires no user credential. The generic harness SSH login is its disposable local test account.