Fix/plugin audit findings (#2031)

* fix(plugins): enforce manifest apiVersion at install and activation

* test(plugins): exercise the absent-apiVersion default

* feat(plugins): generate the core-event catalog into host-facts

* docs(plugins): explain the packing.listBags write-permission binding

* test(plugins): cover apiVersion upload refusal; tidy docblocks and generator header

* fix(plugin-sdk): validate settings options and oauth like the host

* fix(plugin-sdk): model the full manifest shape and stop dropping fields

* fix(plugin-sdk): drop non-string routeProfiles icons like the host

* docs(plugin-sdk): document host limits, oauth contract, and ignored asUserId

* feat(plugin-sdk): enforce daily budgets and meta quotas in the mock host

* feat(plugin-sdk): expose the generated core-event catalog

* feat(plugin-sdk): scaffold trek range >=3.4.0 <5.0.0 ahead of TREK 4.0

* feat(plugin-sdk): scaffold the oauth broker settings for oauth:client plugins

* chore(plugin-sdk): fix stale mirror docs and the colliding permissions export

* docs(plugin-sdk): add the rate-limit string; restore trek docblock; match host meta sizing

* feat(plugin-sdk): raise the scaffold trek floor to 4.0.0

* docs(wiki): bring the plugin pages in line with the current runtime

* docs: bring the workspace CLAUDE.md guides up to date and make them committable

* feat(plugin-sdk): roll back failed publishes, add unrelease, sync the fork, retro-sign updates

* docs(wiki): document the publish rollback, unrelease, fork sync, and retro-signing

* chore(plugin-sdk): 1.6.0
This commit is contained in:
jubnl
2026-08-24 23:48:36 +02:00
committed by GitHub
parent 4bac447ac8
commit d110b3bb6d
48 changed files with 1895 additions and 169 deletions
@@ -33,7 +33,7 @@ import type { PluginDependency } from './install/manifest';
import type { VersionMismatch, PluginDepRow } from './dependencies';
import { parseDependencies, disabledRequiredAddons, resolveDependencyState, enableOrder, findDependentsTransitive, DependencyCycleError } from './dependencies';
import { HTTP_OUTBOUND_PREFIX as HTTP_OUTBOUND } from './protocol/envelope';
import { HTTP_OUTBOUND_PREFIX as HTTP_OUTBOUND, PLUGIN_API_VERSION } from './protocol/envelope';
// Mirrors HOST_RE in install/manifest.ts: an exact hostname or a `*.`-prefixed wildcard
// with a real multi-label suffix. Rejects a bare `*`, a whole-TLD wildcard, a scheme and
@@ -85,7 +85,9 @@ export type PluginDependencyCode =
/** The plugin's declared TREK range doesn't admit the running host. */
| 'TREK_VERSION_INCOMPATIBLE'
/** The plugin never declared a range, so we can't know that it does. */
| 'TREK_VERSION_UNKNOWN';
| 'TREK_VERSION_UNKNOWN'
/** The plugin's manifest apiVersion is newer than this TREK's plugin-API surface. */
| 'API_VERSION_INCOMPATIBLE';
/**
* Thrown when a plugin can't activate because a required addon is disabled, a declared
@@ -478,11 +480,12 @@ export class PluginRuntimeService implements OnModuleInit, OnModuleDestroy {
/**
* Read-only activation gate for one plugin — throws (without mutating) if it may
* not activate. Checks run most- to least-severe: TREK-version compatibility →
* permission re-consent → required addon disabled → missing/mismatched plugin dependency.
* plugin-API version compatibility → permission re-consent → required addon disabled →
* missing/mismatched plugin dependency.
*/
private assertActivatable(id: string, installed: Map<string, PluginDepRow>, consentWiden: boolean): void {
const row = this.db.prepare('SELECT permissions, granted_permissions, dependencies, trek_range FROM plugins WHERE id = ?').get(id) as
| { permissions: string; granted_permissions: string; dependencies: string | null; trek_range: string | null }
const row = this.db.prepare('SELECT permissions, granted_permissions, dependencies, trek_range, api_version FROM plugins WHERE id = ?').get(id) as
| { permissions: string; granted_permissions: string; dependencies: string | null; trek_range: string | null; api_version: number | null }
| undefined;
if (!row) throw new Error(`plugin ${id} not found`);
@@ -505,6 +508,16 @@ export class PluginRuntimeService implements OnModuleInit, OnModuleDestroy {
{ trekRange: row.trek_range, hostVersion: hostVersion() },
);
}
// Same reasoning as the TREK-version gate above: a plugin whose manifest apiVersion
// outpaces this TREK's plugin-API surface can never run correctly, so it must be
// refused before any consent dialog is offered.
const apiVersion = row.api_version ?? 1;
if (apiVersion > PLUGIN_API_VERSION) {
throw new PluginDependencyError(
`plugin requires plugin-API v${apiVersion}; this TREK supports v${PLUGIN_API_VERSION}`,
'API_VERSION_INCOMPATIBLE',
);
}
const declared = parseArray(row.permissions).filter(isKnownPermission);
const granted = parseArray(row.granted_permissions);