Trust Model
Plugins run inside the SSG dashboard, in a sandboxed frame. Each plugin gets its own scoped credential, not the operator token. The trust model has to make that safe.
What gets verified at install
When a user runs ssg plugins install <id> (or clicks Install in the dashboard
marketplace), the daemon performs four independent checks before any plugin
code is extracted to disk:
| # | Check | Failure mode |
|---|---|---|
| 1 | manifest_url is on an allow-listed host (raw.githubusercontent.com or hub.sigmashake.com) | Refuse install. |
| 2 | SHA-256 of the downloaded .tar.gz ≡ manifest.artifact_sha256 | Refuse install. |
| 3 | Ed25519 signature over artifact_sha256 verifies against manifest.signing_fingerprint | Refuse install. |
| 4 | manifest.signing_fingerprint ≡ the hub-recorded author_public_key for the pack | Refuse install. |
Check #2 ensures the tarball wasn't swapped on the CDN. Check #3 ensures only the key holder could have produced this artifact. Check #4 anchors the key to a specific GitHub identity in the hub — so even if attacker-controlled GitHub infra served a different artifact, the signing key cross-check would fail.
The four checks are independent: any one of them passing doesn't help an attacker bypass the others. All four must pass.
Why GitHub releases
Plugin artifacts live on GitHub release assets because:
- GitHub gives every publisher a stable, content-addressable URL that doesn't require us to operate object storage.
- GitHub's tag-based versioning matches plugin semver naturally.
- Publishers already have an identity (GitHub login) the hub uses for attribution.
The hub does not mirror plugin artifacts. If a publisher takes down a release, that version becomes uninstallable. This is intentional — plugin authors retain the right to unpublish.
Publisher keys
Every plugin author signs with one Ed25519 keypair, generated and stored locally:
~/.sigmashake/keys/signing.key # private key (mode 0600)
~/.sigmashake/keys/signing.pub # public key (SPKI)
The private key never leaves the machine that ran ssg keys generate. To
publish from a second machine, copy ~/.sigmashake/keys/ to it manually — do
not re-run ssg keys generate on the second machine (that produces a new
key, which won't match the one your hub account has registered).
The corresponding public key is uploaded to the hub via ssg keys register.
The hub stores it under your GitHub identity (users.public_key column). At
plugin install time, the daemon refuses to install if the manifest's claimed
signing_fingerprint doesn't match the public key on file for the plugin's
listed author.
Key rotation
If your signing key is compromised:
- Stop publishing from the compromised machine immediately.
- Generate a new key on a clean machine, then re-register it with the hub
(
ssg keys generatefollowed byssg keys register). - Open a support ticket (
ssg support) so the old key can be revoked from the hub. The hub keeps an audit log of every key rotation. - Re-publish each of your plugins under the new key. Users will see signature
mismatches on
ssg plugins installuntil they uninstall + reinstall the updated artifact.
What plugins can and cannot do
Verified against handlers_plugin_host.go, plugin_capabilities.go, auth.go
and router.go in the shipped Go ssg (2026-10-13).
Each plugin page is served with a Content-Security-Policy sandbox directive
(allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads, no allow-same-origin), so a
plugin document has an opaque origin. Each plugin receives a separate scoped
credential in __SSG_PLUGIN_CONFIG__.token. No plugin receives the operator
token.
- Can always read four public endpoints:
GET /api/health,GET /api/state/counters,GET /api/json/rules-statsandGET /api/json/mode. - Can call more
/api/routes only if the manifest requests them, the operator grants them, and the route is in a fixed catalog compiled into the binary. The catalog holds read routes andPOST /api/json/eval-dry-run(non-logging, non-mutating). Routes that start processes are never grantable. - A grant is bound to the content hash of the installed artifact. A plugin
upgrade drops every grant. A plugin installed with
ssg plugins install --from <path>is unsigned, has no content hash, and can hold no grants. - Can open network connections only to the origins in its
connect-srcpolicy: the plugin's own loopback serve origin, plushttps://hostorigins (or loopbackhttp) that the operator granted from the manifest request. Wildcards are rejected. - Cannot register new server-side routes.
- Cannot read the user's filesystem directly (browser sandbox).
- Cannot use the operator token or modify SSG signing keys.
Two reviewed first-party plugins (workspace, terminal) and the factory plugin get their own API namespaces. Marketplace and local plugins do not.
The sandbox and the capability grants limit a plugin. They do not remove the need to vet the publisher. The signing key and GitHub identity of the publisher are what the install-time checks anchor to. A plugin is only as trustworthy as its signing key.
The threat we do not fully defend against
A granted capability is real access. Do not grant a plugin capabilities you do not understand, and do not install a plugin you do not trust the publisher of. Treat plugins the way you treat a VS Code extension or a Chrome extension: vet the author, check their reputation, prefer publishers you recognize.
Audit trail
Plugin install and uninstall are not recorded in the local audit log. The
install and uninstall code paths (ssg plugins install and the hub install
handlers) write no audit_log row. The local audit log is the audit_log
table in ~/.sigmashake/rules.db (Windows:
%USERPROFILE%\.sigmashake\rules.db). SSG writes it on a best-effort basis,
and it records governed tool-call evaluations. Query it with
ssg audit search.
SSG keeps an install record next to the plugin files. It holds the source
pack id (hub) or a local source marker (sideload), the content hash, the
signing_fingerprint, and the granted capabilities. This is an install
record. It is not a tamper-evident audit trail.
See also
- Manifest reference — exact fields used at verification
- Publishing — the publish flow that produces signed artifacts