Files
punktfunk-plugin-index/.gitea/pull_request_template.md
T
enricobuehlerandClaude Fable 5 efb37d1826 feat: signed plugin index with validation and publish pipeline
The catalog the Punktfunk plugin store fetches. Served straight out of this
repository over Gitea's anonymous raw endpoint:

  https://git.unom.io/unom/punktfunk-plugin-index/raw/branch/main/v1/index.json
  https://git.unom.io/unom/punktfunk-plugin-index/raw/branch/main/v1/index.json.sig

Hosts verify the ed25519 signature against a compiled-in public key and only
then parse. Verified that the raw endpoint serves blobs byte-for-byte, which
the signature depends on; .gitattributes pins LF so a Windows checkout cannot
break it from the other direction.

Entries pin one exact version plus that version's registry tarball integrity
hash -- no ranges, no "latest". A plugin author publishing a new version
changes nothing for users; the new version becomes installable only when a
reviewer works the checklist and lands a new pinned entry here. That data
shape is what makes "verified on every release" enforceable rather than a
promise.

Seeded with the two first-party plugins, both integrity hashes confirmed
against the live registry:
  - @punktfunk/plugin-rom-manager 0.3.1 (linux, windows)
  - @punktfunk/plugin-playnite    0.1.1 (windows)

Tooling (bun + TypeScript, node builtins only):
  - validate: every field rule the host enforces, plus a live registry
    cross-check that the pinned version exists and its dist.integrity matches
    the pin. Strict on unknown keys, since the host silently drops entries
    that fail validation -- a `min_host` typo would otherwise ship as a
    missing version floor with no error anywhere.
  - sign / verify / keygen: ed25519 over the exact bytes of index.json.
    keygen never prints the private key; verify defaults to the host-pinned
    public key so an index can be audited with no arguments.

CI splits by trust: pull requests run validate only and hold no secrets, so a
fork PR can never reach the signing key or a token that can write to main.
Publishing from main validates, signs, self-verifies, then commits the
signature back. The loop guard is a paths-ignore filter on v1/index.json.sig,
with a [skip ci] marker as a second line of defence; ed25519 determinism means
an unchanged index re-signs to identical bytes and commits nothing at all.

CI signs after the merge, so there is a brief window where index.json is newer
than its signature. It fails closed -- hosts reject the document and keep
their last good cached catalog -- and is documented as such in the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 20:25:16 +02:00

79 lines
3.6 KiB
Markdown

<!--
This is the human half of "verified on every release".
CI can only prove the pinned hash matches what the registry serves. It cannot
tell you what that code DOES. Everything below is the part only a person can
do. A merged PR here makes code installable on other people's machines under
unom's badge -- treat it accordingly.
-->
## What changed
- **Package:**
- **Version:** `` -> `` (previous pinned version -> new pinned version)
- **Type:** <!-- new plugin / version bump / metadata only / security advisory -->
## Diff reviewed
**Tarball compared:** <!-- paste the command you ran, e.g.
npm pack @punktfunk/plugin-foo@0.2.0 --registry https://git.unom.io/api/packages/unom/npm/
npm pack @punktfunk/plugin-foo@0.3.0 --registry https://git.unom.io/api/packages/unom/npm/
then extract both and: diff -ru foo-0.2.0/ foo-0.3.0/
For a NEW plugin there is no previous version -- read the whole thing instead and say so. -->
```
(summary of what actually changed in the published artifact)
```
## Review checklist
Tick each box only after you have personally checked it **against the published
tarball**, not against the git repo. The tarball is what users execute; the repo
is only evidence about it.
- [ ] **Diffed against the previously pinned version.** I compared the new
tarball to the last version pinned in this index and read every change.
(New plugin: I read the entire published tarball.)
- [ ] **No unexpected network endpoints.** Every host the code contacts is
accounted for by the plugin's stated purpose. I grepped for URLs, IPs,
`fetch`/`http`/`net`/`dns`/websocket use, and found no telemetry,
analytics, or beaconing that is not disclosed.
- [ ] **No filesystem access beyond the stated purpose.** Reads and writes stay
within what the plugin is for. No access to credentials, SSH keys, browser
profiles, host config, or unrelated user data.
- [ ] **No obfuscated or minified code where source is expected.** Everything is
readable. Nothing is base64/hex blobs, `eval`, `new Function`, dynamic
`require` of computed strings, or a bundled build I cannot trace to source.
Vendored/bundled dependencies are declared and justified below.
- [ ] **No install lifecycle scripts.** package.json has no `preinstall`,
`install`, `postinstall`, `prepare`, or `prepublish` that executes code on
the user's machine at install time.
- [ ] **Dependencies reviewed and justified.** I reviewed every added or bumped
dependency: each is necessary, from a plausible source, and not
typosquatting a well-known name. New transitive weight is proportionate.
- [ ] **Pinned integrity matches the registry.** I fetched `dist.integrity` from
the registry myself and compared it to the value in this PR -- I did not
only trust CI.
- [ ] **Version is an exact semver.** No range, no `latest`, no `v` prefix, no
wildcard. One immutable version.
- [ ] **Metadata is accurate.** Title, description, icon, author, homepage,
license, `platforms`, and `minHost` all match reality.
- [ ] **`reviewedAt` is today's date** and reflects when *this* review happened.
### Dependencies added or changed
<!-- list each, with why it is needed. "none" is a fine answer. -->
### Anything that gave you pause
<!-- Note anything odd you decided was acceptable, and why. If nothing, say so
explicitly -- "nothing" is a real signal, a blank field is not. -->
---
<!--
Reviewer: if any box cannot be honestly ticked, do not merge. An unmerged PR
costs one release cycle. A bad merge ships code to every host that trusts this
index, signed by unom's key.
-->