Repository navigation
feat: plugins framework (#2913) + disable-non-public-models (#3017) — integrated + security-hardened - #3041
Conversation
- Path traversal guard: validate entryPoint stays within plugin dir - install() now handles direct plugin directories (not just parent dirs) - Non-null assertion replaced with explicit null check - require efficiency: allowedModules map moved outside function - Source wrapper: add newlines to prevent trailing comment issues - Config validation: validate values against configSchema on save - Dynamic import comment: clarify Node.js caching behavior Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Addresses all remaining code review feedback: 1. **Loader rewrite**: Replaced Node.js vm module with child_process.fork() for proper process-level isolation. Complies with Rule 3 (no eval). Each plugin runs in a separate Node.js process with IPC communication. 2. **Auth on all routes**: Added requireManagementAuth to all 6 plugin API route files (list, install, scan, details, activate, deactivate, config). 3. **Env filtering**: Only safe env vars passed to plugin processes unless "env" permission is granted. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
loader.ts:
- Fix IPC: use process.send()/process.on("message") instead of worker_threads.parentPort
- Fix ESM: write host script as .mjs (not .js) to force ESM execution
- Add timeout: 10s default on callHook() with Promise.race
- Add SIGKILL escalation: SIGTERM first, then SIGKILL after 3s grace
- Fix env filtering: use allowlist (safeKeys) instead of passing all env vars
- Clear timeout on successful IPC response (no timer leak)
manager.ts:
- Fix path traversal: use fs.realpath() instead of startsWith()
- Fix imports: use registerHook/unregisterHooks from hooks.ts
- Register hooks individually via registerHook(event, name, handler)
hooks.ts:
- Copied from feat/plugin-custom-hooks (canonical registry)
Phase 1 scaffold for automated provider discovery: - DiscoveryConfig, DiscoveryResult types - probeEndpoint() for URL availability checking - scanProvider() stub (Phase 2 will implement real scanning) - getDiscoveryResults() stub - Default config: disabled (opt-in)
- index.ts: replace console.log/error with pino structured logging - hooks.ts: remove redundant .sort() in emitHookBlocking/runOnResponse (already sorted on registration) - manager.ts: add readFile import
- scanner: 9 tests (discovery, hidden dirs, validation, entry point, multiple) - loader: 5 tests (type contracts, Plugin/PluginContext/PluginResult interfaces) - manager: 6 tests (singleton, lifecycle methods, error on unknown) - Total: 20 tests, all passing
- Path traversal guard: validate entryPoint stays within plugin dir - install() now handles direct plugin directories (not just parent dirs) - Non-null assertion replaced with explicit null check - require efficiency: allowedModules map moved outside function - Source wrapper: add newlines to prevent trailing comment issues - Config validation: validate values against configSchema on save - Dynamic import comment: clarify Node.js caching behavior Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
Addresses all remaining code review feedback: 1. **Loader rewrite**: Replaced Node.js vm module with child_process.fork() for proper process-level isolation. Complies with Rule 3 (no eval). Each plugin runs in a separate Node.js process with IPC communication. 2. **Auth on all routes**: Added requireManagementAuth to all 6 plugin API route files (list, install, scan, details, activate, deactivate, config). 3. **Env filtering**: Only safe env vars passed to plugin processes unless "env" permission is granted. Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
loader.ts:
- Fix IPC: use process.send()/process.on("message") instead of worker_threads.parentPort
- Fix ESM: write host script as .mjs (not .js) to force ESM execution
- Add timeout: 10s default on callHook() with Promise.race
- Add SIGKILL escalation: SIGTERM first, then SIGKILL after 3s grace
- Fix env filtering: use allowlist (safeKeys) instead of passing all env vars
- Clear timeout on successful IPC response (no timer leak)
manager.ts:
- Fix path traversal: use fs.realpath() instead of startsWith()
- Fix imports: use registerHook/unregisterHooks from hooks.ts
- Register hooks individually via registerHook(event, name, handler)
hooks.ts:
- Copied from feat/plugin-custom-hooks (canonical registry)
Phase 1 scaffold for automated provider discovery: - DiscoveryConfig, DiscoveryResult types - probeEndpoint() for URL availability checking - scanProvider() stub (Phase 2 will implement real scanning) - getDiscoveryResults() stub - Default config: disabled (opt-in)
- index.ts: replace console.log/error with pino structured logging - hooks.ts: remove redundant .sort() in emitHookBlocking/runOnResponse (already sorted on registration) - manager.ts: add readFile import
…cation Add `integrity: z.string().optional()` to `PluginManifestSchema` so the SHA-256 tamper-detection check in loader.ts is no longer dead code. The field is opt-in: a plugin without `integrity` loads normally (local- operator trust model). When present, the loader verifies the entry-file hash and throws on mismatch. The schema field includes a doc-comment explaining the security boundary (loopback routing + exec opt-in are the real guards; integrity is tamper-detection only). Existing tests in plugins-signing.test.ts cover all three cases (correct hash passes, wrong hash throws, absent field passes).
…ister IMPORTANT-8: emitHookBlocking (the onRequest/onResponse hot path) was not checking isRateLimited(), unlike emitHook. Add the same guard at the top of the per-handler loop so abusive plugins can't bypass the 100-calls/s cap via the blocking path. IMPORTANT-4: unregisterHooks() now calls rateLimitMap.delete(pluginName) so uninstalled plugins don't leak rate-limit state indefinitely. resetHooks() already called rateLimitMap.clear(); this aligns the partial-unregister path. Tests added to plugins-hooks-rate-limit.test.ts: - emitHookBlocking skips a rate-limited plugin - resetHooks clears rate-limit state so counts start fresh
…ecutions source
IMPORTANT-7: plugin_configure now calls validatePluginConfig() before
updatePluginConfig() when the plugin declares a configSchema. On failure
it returns { success: false, error: "Config validation failed: ..." }
without leaking raw field-level detail. Plugins without a configSchema
skip validation (nothing to validate against). Import of validatePluginConfig
added from manifest.ts.
IMPORTANT-9: plugin_executions description corrected from "plugin_metrics
table" to "plugin_analytics table" — the handler has always read
plugin_analytics (migration 091), never plugin_metrics. Added a doc-
comment to pluginMetrics.ts explaining that plugin_metrics (migration 090)
is a reserved aggregate table whose write path (recordPluginMetric) is not
yet wired into production code, and that the migration should not be removed.
Tests added to plugins-tools.test.ts:
- plugin_configure rejects wrong type (number for string field)
- plugin_configure rejects out-of-range number
- plugin_configure accepts valid config
- plugin_configure allows any config when no configSchema declared
…ted_at MINOR-10: compareSemver now strips a '-prerelease' suffix (e.g. '1.0.0-beta' from a legacy DB row) before parsing segments, and coerces non-numeric segments to 0 instead of NaN. Prevents silent NaN comparisons that could mark '1.0.0-beta' as equal to '1.0.0' via NaN !== NaN short-circuit. compareSemver exported for unit-testability. MINOR-11: updatePluginStatus in db/plugins.ts gains a one-line comment documenting that activated_at is the most-recent activation timestamp, intentionally preserved on deactivation via COALESCE. No logic change. Tests added to plugins-upgrade.test.ts: - compareSemver: valid semver compares correctly - compareSemver: pre-release suffix strips cleanly (no NaN) - compareSemver: NaN segments coerce to 0, result is not NaN
|
🔒 Security hardening complete — a deep code-review of the auto-completed plugin code found 11 issues (3 CRITICAL, 6 IMPORTANT, 2 MINOR); all are now fixed on this branch: CRITICAL:
IMPORTANT: MINOR: Validation: typecheck clean · check:cycles OK · 246/246 tests (incl. 43 security tests + the new hardening tests). Recommend a final human pass on the sandbox trust model before merge. |
There was a problem hiding this comment.
💡 Codex Review
https://github.com/diegosouzapw/OmniRoute/blob/90641886a5694948dee419121206bebcb3cecbd5/src/lib/plugins/manager.ts#L373
Register activated plugins with the request pipeline
When a plugin is activated through the new API/MCP flow, this registers the handlers only in src/lib/plugins/hooks.ts; however the actual chat pipeline still imports runOnRequest/runOnError from @/lib/plugins/index in open-sse/handlers/chatCore.ts, which iterates its separate _plugins registry. As a result, plugins installed and activated by this framework are marked active but their hooks never run for real chat requests.
https://github.com/diegosouzapw/OmniRoute/blob/90641886a5694948dee419121206bebcb3cecbd5/src/lib/plugins/loader.ts#L50
Enforce plugin permissions in the actual loader
This host script imports the plugin entry point in a normal Node child process, so a plugin that declares no permissions can still import('node:child_process'), fs, or use unrestricted network APIs as soon as it is activated. The permission-gated sandbox in pluginWorker.ts is not used by loadPlugin (repo search shows only tests reference it), so the advertised exec permission and OMNIROUTE_PLUGINS_ALLOW_EXEC opt-in are bypassed for any installed plugin.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -70,21 +71,21 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ | |||
| if (!field) continue; // Allow extra keys | |||
| if (field.type === "number" && typeof value === "number") { | |||
There was a problem hiding this comment.
Reject config values with the wrong declared type
For plugins that declare a configSchema, this validation only checks numeric min/max when the value is already a number and select enums, so PUT /api/plugins/:name/config accepts values like { "maxRetries": "five", "debug": "yes", "apiUrl": 123 } even though the manifest declares number/boolean/string fields. That stores invalid config for the plugin to consume, while the SDK/tests expect type validation for these schema types.
Useful? React with 👍 / 👎.
…s-and-model-policy # Conflicts: # tests/unit/apikeypolicy-quota-only.test.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc1b92663c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "/api/services/", // T-10: can run npm install + spawn node processes | ||
| "/api/tools/agent-bridge/", // start/stop MITM server + DNS edits (Hard Rules #15 + #17) | ||
| "/api/tools/traffic-inspector/", // http-proxy listener + system proxy (Hard Rules #15 + #17) | ||
| "/api/plugins/", // plugins: load/execute via worker_threads + child_process (Hard Rules #15 + #17) |
There was a problem hiding this comment.
Add the bare plugins path to the spawn denylist
This adds both /api/plugins/ and the bare /api/plugins route to LOCAL_ONLY_API_PREFIXES, but only the slash-suffixed prefix is listed as spawn-capable. isLocalOnlyBypassableByManageScope() rejects configured bypass prefixes by comparing them against SPAWN_CAPABLE_PREFIXES, so a runtime bypass prefix of /api/plugins is not considered equal to, a child of, or a parent of /api/plugins/; with manage-scope bypass enabled, remote manage-key callers could reach POST /api/plugins even though that endpoint installs/loads local code. Include the bare path in the spawn-capable list as well.
Useful? React with 👍 / 👎.
| const { getPluginAnalytics, getPluginAnalyticsSummary } = await import( | ||
| "../../../src/lib/db/plugins" | ||
| ); |
There was a problem hiding this comment.
Record executions before serving plugin metrics
The tool now reads plugin_analytics, but I searched production code for recordPluginExecution/recordPluginMetric and found no hook or loader path that writes rows when plugins actually run. In normal use, plugin_executions will therefore return empty per-plugin rows and zero summaries no matter how often hooks execute; wire the recording into the hook execution path before switching the MCP tool to this table.
Useful? React with 👍 / 👎.
|
Merged into This integration branch combines two community contributions, both credited in the CHANGELOG and as co-authors on the squash commit:
The original PRs (#2913, #3017) have been closed in favor of this integrated, security-hardened branch. Thank you both! 🚀 |
…e-banner example (#3045) Follow-up to the plugins framework (#3041): wires the plugin hooks end-to-end and adds full test coverage. - Wires `onRequest` / `onResponse` / `onError` hooks into the chat pipeline (`chatCore` now imports from the unified `hooks` registry). - Loads active plugins on server startup (`pluginManager.loadAll()` in `server-init`) so they survive restarts. - Ships a `welcome-banner` example plugin (`examples/plugins/`) + test fixture. - Adds a comprehensive plugin test suite (manifest, db, config, hooks, manager lifecycle, loader IPC, permissions, scanner, welcome-banner e2e). Integration fixes applied during review: - `manager.install` now removes an orphaned `destDir` (DB row gone but files left on disk) before the atomic rename, guarded by path containment — it previously failed with `ENOTEMPTY` (a regression surfaced by the new lifecycle test). - `plugins-db` / `plugins-manager-lifecycle` tests now initialize the DB via the real migration `076` (`getDbInstance`) rather than relying on ambient state, so a missing/renumbered migration fails loudly instead of being masked. 348/348 plugin tests pass; typecheck / cycles clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com>
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (#2859/#3022/#3032), page redesigns (#2827/#2839/#2847/#2849/#2869/#2873), and fixes #2960/#2973/#2984/ #3021/#3029/#3031/#3035/#3036/#3037/#3039/#3043/#3028; folded #2978/#2988/#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…r-inference#3286) Each blocking handler received the original static payload, so a later plugin could not observe an earlier plugin's body/metadata mutations — the accumulated mergedBody/mergedMetadata were only used for the final return value. Feed each handler the chained { ...ctx, body: mergedBody, metadata: mergedMetadata }. Salvaged from the now-closed plugins-v4 branch (cheaper-inference#3221, superseded by the shipped cheaper-inference#3041/cheaper-inference#3045 framework) and adapted to the current release hooks.ts. The other two changes from the original PR don't apply: runOnResponse is fire-and-forget by design on release, and the 086 migration was superseded by 090. Regression test plugins-hook-payload-chaining-3286.test.ts: RED before, GREEN after; existing plugins-hooks suites stay green (19+5). Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…els (cheaper-inference#3041) Integrates two community contributions into release/v3.8.8 with security hardening and conflict resolution. - **Plugins framework** (cheaper-inference#2913 — thanks @oyi77): hooks + registry unification, plugin SDK (`definePlugin`), worker-thread sandbox, per-plugin hook rate limiting, SHA-256 integrity verification, semver-gated upgrade, and execution analytics. Plugin routes are loopback-only (`isLocalOnlyPath`); `child_process` exec is opt-in via `OMNIROUTE_PLUGINS_ALLOW_EXEC` (default off). - **API key option: disable non-published models** (cheaper-inference#3017 — thanks @androw): a per-key flag restricting the key to discovered public models (combos / `auto/*` / `qtSd/*` routing still allowed). Hardening applied during integration: migration renumber (089/090/091), `/api/plugins` LOCAL_ONLY route-guard classification (closes the plugin-RCE vector), atomic install/upgrade with path containment, `O_EXCL` tmp-file creation (TOCTOU), rate-limit-map eviction, `validatePluginConfig` on configure, `buildErrorBody` on all plugin error paths. 246/246 tests; typecheck / cycles / docs-sync clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
…e-banner example (cheaper-inference#3045) Follow-up to the plugins framework (cheaper-inference#3041): wires the plugin hooks end-to-end and adds full test coverage. - Wires `onRequest` / `onResponse` / `onError` hooks into the chat pipeline (`chatCore` now imports from the unified `hooks` registry). - Loads active plugins on server startup (`pluginManager.loadAll()` in `server-init`) so they survive restarts. - Ships a `welcome-banner` example plugin (`examples/plugins/`) + test fixture. - Adds a comprehensive plugin test suite (manifest, db, config, hooks, manager lifecycle, loader IPC, permissions, scanner, welcome-banner e2e). Integration fixes applied during review: - `manager.install` now removes an orphaned `destDir` (DB row gone but files left on disk) before the atomic rename, guarded by path containment — it previously failed with `ENOTEMPTY` (a regression surfaced by the new lifecycle test). - `plugins-db` / `plugins-manager-lifecycle` tests now initialize the DB via the real migration `076` (`getDbInstance`) rather than relying on ambient state, so a missing/renumbered migration fails loudly instead of being masked. 348/348 plugin tests pass; typecheck / cycles clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com>
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (cheaper-inference#2859/cheaper-inference#3022/cheaper-inference#3032), page redesigns (cheaper-inference#2827/cheaper-inference#2839/cheaper-inference#2847/cheaper-inference#2849/cheaper-inference#2869/cheaper-inference#2873), and fixes cheaper-inference#2960/cheaper-inference#2973/cheaper-inference#2984/ cheaper-inference#3021/cheaper-inference#3029/cheaper-inference#3031/cheaper-inference#3035/cheaper-inference#3036/cheaper-inference#3037/cheaper-inference#3039/cheaper-inference#3043/cheaper-inference#3028; folded cheaper-inference#2978/cheaper-inference#2988/cheaper-inference#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…els (cheaper-inference#3041) Integrates two community contributions into release/v3.8.8 with security hardening and conflict resolution. - **Plugins framework** (cheaper-inference#2913 — thanks @oyi77): hooks + registry unification, plugin SDK (`definePlugin`), worker-thread sandbox, per-plugin hook rate limiting, SHA-256 integrity verification, semver-gated upgrade, and execution analytics. Plugin routes are loopback-only (`isLocalOnlyPath`); `child_process` exec is opt-in via `OMNIROUTE_PLUGINS_ALLOW_EXEC` (default off). - **API key option: disable non-published models** (cheaper-inference#3017 — thanks @androw): a per-key flag restricting the key to discovered public models (combos / `auto/*` / `qtSd/*` routing still allowed). Hardening applied during integration: migration renumber (089/090/091), `/api/plugins` LOCAL_ONLY route-guard classification (closes the plugin-RCE vector), atomic install/upgrade with path containment, `O_EXCL` tmp-file creation (TOCTOU), rate-limit-map eviction, `validatePluginConfig` on configure, `buildErrorBody` on all plugin error paths. 246/246 tests; typecheck / cycles / docs-sync clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
…e-banner example (cheaper-inference#3045) Follow-up to the plugins framework (cheaper-inference#3041): wires the plugin hooks end-to-end and adds full test coverage. - Wires `onRequest` / `onResponse` / `onError` hooks into the chat pipeline (`chatCore` now imports from the unified `hooks` registry). - Loads active plugins on server startup (`pluginManager.loadAll()` in `server-init`) so they survive restarts. - Ships a `welcome-banner` example plugin (`examples/plugins/`) + test fixture. - Adds a comprehensive plugin test suite (manifest, db, config, hooks, manager lifecycle, loader IPC, permissions, scanner, welcome-banner e2e). Integration fixes applied during review: - `manager.install` now removes an orphaned `destDir` (DB row gone but files left on disk) before the atomic rename, guarded by path containment — it previously failed with `ENOTEMPTY` (a regression surfaced by the new lifecycle test). - `plugins-db` / `plugins-manager-lifecycle` tests now initialize the DB via the real migration `076` (`getDbInstance`) rather than relying on ambient state, so a missing/renumbered migration fails loudly instead of being masked. 348/348 plugin tests pass; typecheck / cycles clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com>
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (cheaper-inference#2859/cheaper-inference#3022/cheaper-inference#3032), page redesigns (cheaper-inference#2827/cheaper-inference#2839/cheaper-inference#2847/cheaper-inference#2849/cheaper-inference#2869/cheaper-inference#2873), and fixes cheaper-inference#2960/cheaper-inference#2973/cheaper-inference#2984/ cheaper-inference#3021/cheaper-inference#3029/cheaper-inference#3031/cheaper-inference#3035/cheaper-inference#3036/cheaper-inference#3037/cheaper-inference#3039/cheaper-inference#3043/cheaper-inference#3028; folded cheaper-inference#2978/cheaper-inference#2988/cheaper-inference#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
…els (cheaper-inference#3041) Integrates two community contributions into release/v3.8.8 with security hardening and conflict resolution. - **Plugins framework** (cheaper-inference#2913 — thanks @oyi77): hooks + registry unification, plugin SDK (`definePlugin`), worker-thread sandbox, per-plugin hook rate limiting, SHA-256 integrity verification, semver-gated upgrade, and execution analytics. Plugin routes are loopback-only (`isLocalOnlyPath`); `child_process` exec is opt-in via `OMNIROUTE_PLUGINS_ALLOW_EXEC` (default off). - **API key option: disable non-published models** (cheaper-inference#3017 — thanks @androw): a per-key flag restricting the key to discovered public models (combos / `auto/*` / `qtSd/*` routing still allowed). Hardening applied during integration: migration renumber (089/090/091), `/api/plugins` LOCAL_ONLY route-guard classification (closes the plugin-RCE vector), atomic install/upgrade with path containment, `O_EXCL` tmp-file creation (TOCTOU), rate-limit-map eviction, `validatePluginConfig` on configure, `buildErrorBody` on all plugin error paths. 246/246 tests; typecheck / cycles / docs-sync clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
…e-banner example (cheaper-inference#3045) Follow-up to the plugins framework (cheaper-inference#3041): wires the plugin hooks end-to-end and adds full test coverage. - Wires `onRequest` / `onResponse` / `onError` hooks into the chat pipeline (`chatCore` now imports from the unified `hooks` registry). - Loads active plugins on server startup (`pluginManager.loadAll()` in `server-init`) so they survive restarts. - Ships a `welcome-banner` example plugin (`examples/plugins/`) + test fixture. - Adds a comprehensive plugin test suite (manifest, db, config, hooks, manager lifecycle, loader IPC, permissions, scanner, welcome-banner e2e). Integration fixes applied during review: - `manager.install` now removes an orphaned `destDir` (DB row gone but files left on disk) before the atomic rename, guarded by path containment — it previously failed with `ENOTEMPTY` (a regression surfaced by the new lifecycle test). - `plugins-db` / `plugins-manager-lifecycle` tests now initialize the DB via the real migration `076` (`getDbInstance`) rather than relying on ambient state, so a missing/renumbered migration fails loudly instead of being masked. 348/348 plugin tests pass; typecheck / cycles clean. Co-authored-by: oyi77 <14921983+oyi77@users.noreply.github.com>
…env-doc fixes - bump package.json / open-sse / electron / openapi / llm.txt to 3.8.8 - restructure CHANGELOG: Unreleased -> [3.8.8], dedup broken Notion/MCP block (was 12x) - add every PR since v3.8.7 that was missing: Quota Share Engine (cheaper-inference#2859/cheaper-inference#3022/cheaper-inference#3032), page redesigns (cheaper-inference#2827/cheaper-inference#2839/cheaper-inference#2847/cheaper-inference#2849/cheaper-inference#2869/cheaper-inference#2873), and fixes cheaper-inference#2960/cheaper-inference#2973/cheaper-inference#2984/ cheaper-inference#3021/cheaper-inference#3029/cheaper-inference#3031/cheaper-inference#3035/cheaper-inference#3036/cheaper-inference#3037/cheaper-inference#3039/cheaper-inference#3043/cheaper-inference#3028; folded cheaper-inference#2978/cheaper-inference#2988/cheaper-inference#3041 - insert [3.8.8] section into all 41 i18n CHANGELOGs + sync llm.txt mirrors - document OMNIROUTE_PLUGINS_ALLOW_EXEC in .env.example + ENVIRONMENT.md (env-doc-sync gap)
Resumo
Integra duas PRs de comunidade em uma branch consolidada →
release/v3.8.8, resolvidas com segurança (sem regressão, sem quebra de padrão, sem abrir RCE):#3017 — disable_non_public_models
disable_non_public_modelscoexiste comallowed_quotas+stream_default_modenoapi_keys(merge cuidadoso do core).api_key_stream_default_mode).isModelAllowedForKeyhoisted (perf).auto/*, combos eqtSd/*(quota) bypassam (são combo-routed). Testado.#2913 — Plugins framework
db/migrations/*removidos; migrations plugin emsrc/lib/db/migrations/renumeradas:090_plugin_metrics,091_plugin_analytics(esta estava FALTANDO — ver Segurança).🔒 Segurança (3 correções + 1 masking pego)
/api/plugins/classificado comoLOCAL_ONLYemrouteGuard.ts(loopback enforçado antes da auth; Hard Rule feat(npm): add npm package publishing with CLI entry point #15/fix(ci): explicit .npmrc auth for npm publish #17). Plugins spawnam worker_threads +child_process— sem isso, JWT vazado via túnel = execução remota.child_process.execopt-in: o sandbox só expõechild_processquandoOMNIROUTE_PLUGINS_ALLOW_EXEC=1(default OFF; senão throw).vm.runInContextsempre com timeout. Comentário documenta quevm≠fronteira de segurança (execução só-loopback).buildErrorBody()(semerr.messagecru).recordPluginExecution/getPluginAnalyticsfaziamINSERT/FROM plugin_analyticsmas não havia migration (o teste criava a tabela inline, mascarando) → quebraria em produção. Adicionei a migration canônica 091 + alinhei o teste pra depender dela.Validação (branch integrado)
typecheck:corelimpo ·check:cyclesOK (236) ·check:docs-syncPASSapikeypolicy-quota-only(que estava vermelho na release por causa do rename qtSd/ do feat(quota): Pool Groups + correções de enforcement → v3.8.8 (+ authz peer-IP, screen fixes) #3022).Créditos no CHANGELOG:
(#2913 — thanks @oyi77),(#3017 — thanks @androw). As PRs originais ficam abertas (não fechar — crédito).🤖 Generated with Claude Code