Skip to content

feat(plugins): report setup descriptor drift#71194

Merged
vincentkoc merged 1 commit intomainfrom
feat-plugin-setup-drift-diagnostics
Apr 24, 2026
Merged

feat(plugins): report setup descriptor drift#71194
vincentkoc merged 1 commit intomainfrom
feat-plugin-setup-drift-diagnostics

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

  • Problem: setup descriptors can drift from setup-api runtime registrations with no first-class signal.
  • Why it matters: phase 2 needs descriptor-first setup discovery while preserving legacy setup runtime compatibility.
  • What changed: added additive setup registry diagnostics for missing/undeclared provider and CLI backend registrations, plus docs and changelog coverage.
  • What did NOT change (scope boundary): existing setup runtime registrations are still accepted; this PR does not enforce or reject drift.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: N/A
  • Missing detection / guardrail: setup registry had no diagnostic surface for descriptor/runtime disagreement.
  • Contributing context (if known): plugin architecture phase 2 is moving setup discovery toward manifest descriptors without breaking existing setup-api plugins.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/plugins/setup-registry.test.ts
  • Scenario the test should lock in: mismatched setup descriptors emit diagnostics while runtime registrations remain available.
  • Why this is the smallest reliable guardrail: the registry owns setup descriptor normalization and runtime setup registration collection.
  • Existing test that already covers this (if any): N/A
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Plugin setup registry results now include diagnostics when explicit setup.providers or setup.cliBackends descriptors disagree with setup-api runtime registrations.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 / pnpm
  • Model/provider: N/A
  • Integration/channel (if any): plugins setup registry
  • Relevant config (redacted): N/A

Steps

  1. Run pnpm test src/plugins/setup-registry.test.ts.
  2. Run pnpm check:changed.

Expected

  • Focused registry tests pass.
  • Changed gate passes for core, core tests, and docs lanes.

Actual

  • Focused registry tests passed: 19 tests.
  • pnpm check:changed passed: 101 files, 1190 tests.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: descriptor-only registry shape, descriptor/runtime drift diagnostics, matching descriptor/runtime no-drift path.
  • Edge cases checked: explicit provider/backend descriptors missing runtime registrations and runtime registrations missing explicit descriptors.
  • What you did not verify: full production build.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: callers might treat new diagnostics as hard failures.
    • Mitigation: docs explicitly state diagnostics are additive and do not reject legacy setup plugins.

@vincentkoc vincentkoc self-assigned this Apr 24, 2026
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: S maintainer Maintainer-authored PR labels Apr 24, 2026
@vincentkoc vincentkoc marked this pull request as ready for review April 24, 2026 18:14
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 24, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Potential CPU/memory exhaustion from quadratic drift-diagnostics checks in setup registry resolution
1. 🔵 Potential CPU/memory exhaustion from quadratic drift-diagnostics checks in setup registry resolution
Property Value
Severity Low
CWE CWE-400
Location src/plugins/setup-registry.ts:408-429

Description

pushSetupDescriptorDriftDiagnostics performs nested membership checks between declared IDs from the plugin manifest and runtime-registered providers/backends from plugin setup code.

  • For providers, it uses .some() inside loops in both directions, leading to O(n*m) comparisons.
  • Additionally, it pushes a diagnostic object per mismatch with attacker-controlled strings (declaredId / provider.id), which can further increase memory usage.
  • A malicious or buggy plugin (or manifest) can declare/register extremely large numbers of providers/backends, causing significant CPU time and memory consumption during resolvePluginSetupRegistry() (typically executed at startup / registry build time).

Vulnerable code (quadratic checks for providers):

for (const declaredId of declaredProviderIds) {
  if (!params.providers.some((provider) => matchesProvider(provider, declaredId))) {
    params.diagnostics.push({ ... });
  }
}
for (const provider of params.providers) {
  if (!declaredProviderIds.some((declaredId) => matchesProvider(provider, declaredId))) {
    params.diagnostics.push({ ... });
  }
}

Recommendation

Avoid quadratic checks and cap diagnostic growth.

  • Precompute normalized IDs into Sets/Maps and compare in O(n+m).
  • Consider limiting the maximum number of providers/backends a single plugin can register/declare, and/or limit the number of diagnostics stored per plugin.

Example (providers) using sets:

const declared = new Set(declaredProviderIds.map((id) => normalizeProviderId(id)).filter(Boolean));
const runtime = new Set(params.providers.map((p) => normalizeProviderId(p.id)).filter(Boolean));

for (const id of declared) {
  if (!runtime.has(id)) {// push diagnostic
  }
}
for (const id of runtime) {
  if (!declared.has(id)) {// push diagnostic
  }
}

This reduces CPU usage and makes registry building resilient against pathological plugin inputs.


Analyzed PR: #71194 at commit 75ba9cf

Last updated on: 2026-04-24T18:16:46Z

@vincentkoc vincentkoc merged commit 6bc0dc8 into main Apr 24, 2026
86 of 88 checks passed
@vincentkoc vincentkoc deleted the feat-plugin-setup-drift-diagnostics branch April 24, 2026 18:15
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR adds additive drift diagnostics to the setup registry, emitting structured PluginSetupRegistryDiagnostic entries when setup.providers or setup.cliBackends manifest descriptors disagree with what setup-api runtime registrations actually produce. Existing runtime behaviour and legacy plugin compatibility are preserved — diagnostics are informational only.

Confidence Score: 5/5

Safe to merge — all findings are P2 (no enforcement change, no breaking contract).

The implementation is correct: provider drift detection properly uses matchesProvider (honours aliases), CLI backend detection uses normalized-ID maps, and diagnostics are scoped to plugins with explicit descriptors. The two P2 findings are a partial-throw edge case in the diagnostic surface (low risk for a non-enforcing feature) and a missing no-drift test case for setup.providers. Neither blocks merge.

src/plugins/setup-registry.test.ts — the no-drift path for setup.providers is not covered.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/setup-registry.ts
Line: 563-572

Comment:
**Partial registration silently skips drift diagnostics**

If `setupRegistration.register(api)` throws mid-execution, any providers or CLI backends already pushed into `recordProviders`/`recordCliBackends` (and the global `providers`/`cliBackends` lists via the handler closures) are retained in the registry, but the `catch { continue }` branch skips `pushSetupDescriptorDriftDiagnostics`. A plugin that throws half-way will have partial registrations visible to callers with zero drift diagnostics reported, making the diagnostic surface incomplete for that plugin. Given that this is a diagnostic-only feature (no enforcement), the risk is low — but callers who rely on diagnostics to audit descriptor drift may get a false-clean signal for a misbehaving plugin.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/plugins/setup-registry.test.ts
Line: 457-461

Comment:
**No-drift path for `setup.providers` is untested**

The "does not report drift" test uses `mockOpenAiCliBackendRegistration`, which never sets `setup.providers` in the manifest — so `declaredProviderIds` is always `undefined` and the provider-matching branch in `pushSetupDescriptorDriftDiagnostics` is never exercised on the no-drift path. A regression in `matchesProvider` that produces false-positive drift for correctly-matching providers would pass this test suite undetected. A second no-drift fixture that declares `setup.providers: [{ id: "openai" }]` and registers a provider with `id: "openai"` would close this gap.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(plugins): report setup descriptor d..." | Re-trigger Greptile

Comment on lines 563 to 572
} catch {
continue;
}
pushSetupDescriptorDriftDiagnostics({
record,
providers: recordProviders,
cliBackends: recordCliBackends,
diagnostics,
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Partial registration silently skips drift diagnostics

If setupRegistration.register(api) throws mid-execution, any providers or CLI backends already pushed into recordProviders/recordCliBackends (and the global providers/cliBackends lists via the handler closures) are retained in the registry, but the catch { continue } branch skips pushSetupDescriptorDriftDiagnostics. A plugin that throws half-way will have partial registrations visible to callers with zero drift diagnostics reported, making the diagnostic surface incomplete for that plugin. Given that this is a diagnostic-only feature (no enforcement), the risk is low — but callers who rely on diagnostics to audit descriptor drift may get a false-clean signal for a misbehaving plugin.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/setup-registry.ts
Line: 563-572

Comment:
**Partial registration silently skips drift diagnostics**

If `setupRegistration.register(api)` throws mid-execution, any providers or CLI backends already pushed into `recordProviders`/`recordCliBackends` (and the global `providers`/`cliBackends` lists via the handler closures) are retained in the registry, but the `catch { continue }` branch skips `pushSetupDescriptorDriftDiagnostics`. A plugin that throws half-way will have partial registrations visible to callers with zero drift diagnostics reported, making the diagnostic surface incomplete for that plugin. Given that this is a diagnostic-only feature (no enforcement), the risk is low — but callers who rely on diagnostics to audit descriptor drift may get a false-clean signal for a misbehaving plugin.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +457 to 461
expect(resolvePluginSetupRegistry({ env: {} }).diagnostics).toEqual([]);
});

it("does not load setup-api modules from the current working directory", () => {
const pluginRoot = makeTempDir();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No-drift path for setup.providers is untested

The "does not report drift" test uses mockOpenAiCliBackendRegistration, which never sets setup.providers in the manifest — so declaredProviderIds is always undefined and the provider-matching branch in pushSetupDescriptorDriftDiagnostics is never exercised on the no-drift path. A regression in matchesProvider that produces false-positive drift for correctly-matching providers would pass this test suite undetected. A second no-drift fixture that declares setup.providers: [{ id: "openai" }] and registers a provider with id: "openai" would close this gap.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/setup-registry.test.ts
Line: 457-461

Comment:
**No-drift path for `setup.providers` is untested**

The "does not report drift" test uses `mockOpenAiCliBackendRegistration`, which never sets `setup.providers` in the manifest — so `declaredProviderIds` is always `undefined` and the provider-matching branch in `pushSetupDescriptorDriftDiagnostics` is never exercised on the no-drift path. A regression in `matchesProvider` that produces false-positive drift for correctly-matching providers would pass this test suite undetected. A second no-drift fixture that declares `setup.providers: [{ id: "openai" }]` and registers a provider with `id: "openai"` would close this gap.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant