Skip to content

feat(#3298): scaffold boost-backend with ProviderManager, extension point, and security middleware#3466

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
feat/3298-boost-backend-scaffold
Open

feat(#3298): scaffold boost-backend with ProviderManager, extension point, and security middleware#3466
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
feat/3298-boost-backend-scaffold

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Implements the core boost-backend plugin package:

  • ProviderManager: manages registered AI providers with hot-swap support
    (registerProvider, getActiveProvider, switchProvider)
  • boostProviderExtensionPoint: extension point in boost-node for provider
    modules to register their implementations
  • boostAiProviderServiceFactory: default service factory resolving to the
    active provider via ProviderManager
  • authorizeLifecycleAction: Express middleware implementing fine-grained
    permission check -> boost.admin fallback -> 403 pattern
  • validateSecurityMode: rejects 'none' with clear error, warns when
    development-only-no-auth is used in production (NODE_ENV=production)
  • Permission registration: all 23 boost permissions registered via
    permissionsRegistry.addPermissions()
  • Resource loader placeholders for agents and tools (store integration
    deferred to later issues)

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com


Closes #3298

Post-script verification

  • Branch is not main/master (feat/3298-boost-backend-scaffold)
  • Secret scan passed (gitleaks — 3036a95068caf62a3ac06568b0d21dbc25322ac9..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

@fullsend-ai-coder fullsend-ai-coder Bot requested review from a team, durandom and gabemontero as code owners June 19, 2026 05:12
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 19, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-boost-backend
  • @red-hat-developer-hub/backstage-plugin-boost-common
  • @red-hat-developer-hub/backstage-plugin-boost-node

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-boost-backend workspaces/boost/plugins/boost-backend none v0.1.1
@red-hat-developer-hub/backstage-plugin-boost-common workspaces/boost/plugins/boost-common none v0.1.1
@red-hat-developer-hub/backstage-plugin-boost-node workspaces/boost/plugins/boost-node none v0.1.1

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

⚠️ JUnit XML file not found

The CLI was unable to find any JUnit XML files to upload.
For more help, visit our troubleshooting guide.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:14 AM UTC · Completed 5:31 AM UTC
Commit: 3036a95 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [logic-error] workspaces/boost/plugins/boost-backend/src/plugin.ts:47 — The boostAiProviderServiceFactory calls providerManager.getActiveProvider() eagerly inside factory(), which executes once at service-resolution time. If no provider module has registered yet when the factory resolves, getActiveProvider() throws 'No AI provider is registered'. Even if initialization order happens to work, the factory returns a fixed reference — subsequent switchProvider() calls won't affect consumers that already resolved the service.
    Remediation: Change the factory to return a lazy wrapper/proxy that delegates chat, chatStream, and descriptor to providerManager.getActiveProvider() at invocation time rather than resolution time.

Medium

  • [fail-open] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:63 — When boost.security.mode is absent from configuration, validateSecurityMode defaults to 'development-only-no-auth'. The production warning relies on NODE_ENV=production which may not be set in all production-like deployments. While the security mode is currently validated and logged but not yet consumed by downstream logic (making this a latent design concern rather than an active vulnerability), defaulting to the least restrictive mode is a fail-open pattern that should be corrected before the mode is wired up.
    Remediation: Default to the most restrictive mode ('full') when no mode is configured, or at minimum require explicit opt-in for development-only-no-auth.

  • [api-contract] workspaces/boost/plugins/boost-backend/src/plugin.ts:29 — Module-level singleton const providerManager = new ProviderManager() couples the factory to the plugin module's import graph. If imported from different module instances (e.g., different package versions in the dependency tree), they'd operate on different ProviderManager instances. Deviates from Backstage DI patterns where state is created inside register().
    Remediation: Move new ProviderManager() inside the register() function body and wire through Backstage's DI (e.g., an internal serviceRef).

Low

  • [security-mode-unused] workspaces/boost/plugins/boost-backend/src/plugin.ts:127 — The validated securityMode value is computed and logged but never consumed by any downstream logic. Expected for a scaffold PR but worth tracking.

  • [auth-bypass] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:163 — The authorizeLifecycleAction middleware falls back to boost.admin when a fine-grained permission is denied. While documented as intentional, this means admin holders implicitly bypass all fine-grained permissions, including future separation-of-duties controls.

  • [resource-loader-placeholder] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:195 — Both createAgentResourceLoader and createToolResourceLoader always return undefined, and _resourceLoader is never called. Documented as placeholder behavior for the scaffold.

  • [test-inadequate] workspaces/boost/plugins/boost-backend/src/middleware/security.test.ts:241 — Missing tests for httpAuth.credentials() rejection and permissions.authorize() errors being forwarded to next().

  • [unused-dependency] workspaces/boost/plugins/boost-backend/src/plugin.ts:79httpAuth declared in deps but not destructured; permissions destructured as _permissions but unused.

  • [race-condition] workspaces/boost/plugins/boost-backend/src/provider/ProviderManager.ts:39 — No synchronization in ProviderManager, though safe in Node.js single-threaded synchronous context.

  • [error-handling-pattern] workspaces/boost/plugins/boost-backend/src/provider/ProviderManager.ts:57 — Uses plain Error instead of @backstage/errors classes (NotFoundError, ConflictError), unlike security.ts which uses NotAllowedError.

  • [export-style] workspaces/boost/plugins/boost-backend/src/index.ts:23 — Exports more items than typical for backend plugins in this repo. Exporting ProviderManager as a public class creates a larger contract surface.

  • [type-safety] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:17authorizeLifecycleAction accepts BasicPermission but some boost permissions are ResourcePermission. TypeScript prevents misuse at compile time, but the gap between available permissions and middleware capability is notable.

Info

  • [unauthenticated-endpoint] workspaces/boost/plugins/boost-backend/src/plugin.ts:131/health endpoint is unauthenticated with static response. Standard Backstage pattern.

  • [silent-default] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:42 — Defaults to dev mode without logging the default case. The caller does log the resulting mode.

  • [permission-registration] workspaces/boost/plugins/boost-backend/src/plugin.ts:100 — All 23 boost permissions registered with the Backstage permission framework.

  • [backward-compatibility] workspaces/boost/plugins/boost-node/src/index.ts:24 — boost-node gains two new additive exports. No existing exports changed. Backward compatible.

Previous run

Review

Findings

Critical

  • [logic-error] workspaces/boost/plugins/boost-backend/src/plugin.ts:48 — The service factory's factory() method calls providerManager.getActiveProvider() eagerly at factory creation time and returns the result directly. This will throw 'No AI provider is registered' if no provider module has registered yet (extension point registration order is not guaranteed relative to service factory resolution). Additionally, the factory returns the AgenticProvider object directly — not a lazy proxy — so runtime provider switching via switchProvider() will not be reflected; consumers hold a stale reference.
    Remediation: Return a lazy proxy that delegates to providerManager.getActiveProvider() on each call, or return the ProviderManager itself and update the service ref type accordingly.

Medium

  • [api-contract] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:109authorizeLifecycleAction accepts BasicPermission but several boost-common permissions are ResourcePermission types. The TypeScript constraint prevents passing resource-scoped permissions. When ResourcePermission support is added, widening the parameter type will be a public API change.
    Remediation: Widen the parameter type to accept Permission now, or add a runtime check that rejects resource-scoped permissions with a clear error.

  • [fail-open] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:74validateSecurityMode() defaults to 'development-only-no-auth' when no config is provided — a fail-open default. While the mode is currently not wired to any auth gate (auth always runs), the default creates risk for when the mode is eventually consumed. The production warning is advisory-only.
    Remediation: Change the default to 'full' (fail-closed). Require explicit config for dev-no-auth and refuse to start in production unless mode is 'plugin-only' or 'full'.

  • [scope-alignment] workspaces/boost/plugins/boost-backend/src/plugin.ts:93securityMode is computed from config and logged but never consumed by any middleware or auth gate. The value is dead code in the current implementation.
    Remediation: Document that mode wiring is deferred, or enforce: throw error (not warn) when NODE_ENV=production with 'development-only-no-auth'.

  • [permission-expansion] workspaces/boost/plugins/boost-backend/src/plugin.ts:100 — Plugin registers 23 permissions via permissionsRegistry.addPermissions() and createPermissionIntegrationRouter but does not register any permission rules or resource types. Without rules, conditional policies for the 12 resource-scoped permissions cannot be evaluated.
    Remediation: Add a TODO comment noting that rules and resourcePermissions must be registered. Confirm the framework fails closed when rules are missing.

  • [architectural-conflict] workspaces/boost/plugins/boost-backend/src/plugin.ts:29 — Module-level singleton ProviderManager is instantiated outside any Backstage lifecycle. Cannot be reset between tests, couples service factory to module scope.
    Remediation: Consider moving instantiation inside register() and wiring through Backstage DI mechanisms.

  • [error-handling-idiom] workspaces/boost/plugins/boost-backend/src/provider/ProviderManager.ts:57getActiveProvider() throws plain Error instead of @backstage/errors types. The package already depends on @backstage/errors. Using plain Error means Backstage error middleware cannot map to a proper HTTP status.
    Remediation: Use NotFoundError or ServiceUnavailableError from @backstage/errors.

Low

  • [error-handling] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:115 — Destructuring const [decision] = await permissions.authorize(...) will produce undefined if the service returns an empty array. A defensive guard would produce a clearer error message.
  • [test-inadequate] workspaces/boost/plugins/boost-backend/src/middleware/security.test.ts:240authorizeLifecycleAction tests don't cover error paths (httpAuth/permissions throwing).
  • [test-inadequate] workspaces/boost/plugins/boost-backend/src/middleware/security.test.ts:170 — No test for empty string security mode case.
  • [edge-case] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:82SecurityMode type and VALID_SECURITY_MODES array are manually synced. Use as const derivation for compile-time safety.
  • [scope-creep] workspaces/boost/plugins/boost-backend/src/provider/ProviderManager.ts:71switchProvider method is not authorized by issue boost-backend — Core plugin scaffold, ProviderManager, and security middleware (issue 2 of 15) #3298.
  • [scope-alignment] workspaces/boost/plugins/boost-backend/src/plugin.ts:87permissions service injected but unused (_permissions).
  • [api-completeness] workspaces/boost/plugins/boost-backend/src/index.ts:27ProviderManager exported as concrete class exposes switchProvider() to all consumers; consider a narrower interface.
  • [api-consistency] workspaces/boost/plugins/boost-backend/src/index.ts:29 — Placeholder resource loaders exported as public API; consider @alpha/@beta tags.
  • [pattern-inconsistency] workspaces/boost/plugins/boost-backend/package.json:68 — Test script flags (--passWithNoTests --coverage) differ from siblings.
  • [pattern-inconsistency] workspaces/boost/plugins/boost-backend/package.json:65 — Extra scripts (lint:check, lint:fix, tsc, start) absent in siblings.
  • [pattern-inconsistency] workspaces/boost/plugins/boost-backend/package.json:38exports/typesVersions fields absent in siblings.
  • [architectural-conflict] workspaces/boost/plugins/boost-backend/src/provider/ProviderManager.ts:34 — Raw Map usage defensible (non-serializable instances); add explanatory comment re: AGENTS.md cache rule.
  • [api-design] workspaces/boost/plugins/boost-backend/src/middleware/security.ts:111ResourceLoader return type not backed by shared type in boost-common; formalize when loaders are implemented.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

* Install this alongside the plugin in the backend:
*
* ```ts
* backend.add(import('@red-hat-developer-hub/backstage-plugin-boost-backend'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[critical] logic-error

The service factory's factory() method calls providerManager.getActiveProvider() eagerly at factory creation time and returns its result directly. It will throw 'No AI provider is registered' if no provider module has registered yet. Moreover, the factory returns the AgenticProvider object directly (not a lazy proxy), so switching providers at runtime via switchProvider() will not be reflected — consumers hold a stale reference.

Suggested fix: Return a lazy proxy that delegates to providerManager.getActiveProvider() on each call, or return the ProviderManager itself (updating the service ref type accordingly).

* Returns the resource metadata needed for conditional permission checks,
* or `undefined` if the resource is not found.
*
* @public

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] api-contract

authorizeLifecycleAction accepts BasicPermission but several boost-common permissions are ResourcePermission types. The TypeScript type constraint prevents passing resource-scoped permissions. The JSDoc notes this is intentional scaffolding, but when ResourcePermission support is added, widening the parameter type will be a public API change.

Suggested fix: Widen the parameter type to accept Permission now to avoid a future breaking change, or add a runtime check that rejects resource-scoped permissions with a clear error.

}

if (!mode) {
return 'development-only-no-auth';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] fail-open

validateSecurityMode() defaults to 'development-only-no-auth' when no config is provided — a fail-open default. While the mode is currently not wired to any auth gate (authorizeLifecycleAction always runs permission checks regardless), the default creates risk for when the mode is eventually consumed. The production warning is advisory-only and does not block startup.

Suggested fix: Change the default to 'full' (fail-closed). If dev-no-auth must be supported, require explicit config and refuse to start in production unless mode is 'plugin-only' or 'full'.

permissions: coreServices.permissions,
permissionsRegistry: coreServices.permissionsRegistry,
},
async init({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] scope-alignment

securityMode is computed from config and logged but never passed to middleware or used to gate authorization. The value is dead code in the current implementation. When a future developer wires this up, the fail-open default becomes a real concern.

Suggested fix: Either remove 'development-only-no-auth' mode or enforce it: throw error (not just warn) when NODE_ENV=production. Document that securityMode wiring is deferred to a future PR.

permissions: _permissions,
permissionsRegistry,
}) {
logger.info('Initializing boost backend plugin');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] permission-expansion

Plugin registers 23 permissions and passes them to createPermissionIntegrationRouter but does not register any permission rules or resource types. Without rule registration, conditional policies for resource permissions will fail to evaluate. The framework likely fails closed (DENY) but this should be confirmed.

Suggested fix: Add a TODO comment noting that rules and resourcePermissions must be registered when conditional permission evaluation is implemented. Confirm the framework fails closed when rules are missing.

"type": "git",
"url": "git+https://github.com/redhat-developer/rhdh-plugins.git",
"directory": "workspaces/boost/plugins/boost-backend"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] pattern-inconsistency

test script adds --passWithNoTests --coverage flags while sibling packages use bare 'backstage-cli package test'.

Suggested fix: Align with sibling convention.

"dist"
],
"repository": {
"type": "git",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] pattern-inconsistency

lint:check, lint:fix, tsc, and start scripts not present in sibling packages. Backend plugins may legitimately need start, but lint:check/lint:fix are redundant with lint.

Suggested fix: Remove redundant scripts or add to all siblings for consistency.

"dependencies": {
"@backstage/backend-plugin-api": "^1.9.1",
"@backstage/errors": "^1.2.7",
"@backstage/plugin-permission-common": "^0.9.7",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] pattern-inconsistency

exports and typesVersions fields present but absent in sibling packages.

Suggested fix: Remove or add to siblings for consistency.

private activeProvider: AgenticProvider | undefined;
private readonly providers = new Map<string, AgenticProvider>();

/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] architectural-conflict

ProviderManager uses raw Map while AGENTS.md says not to create raw Map caches. However, this Map holds live provider instances (non-serializable), not cached data — the rule does not apply.

Suggested fix: Add comment explaining why this Map is not subject to the cacheService rule.

*
* @public
*/
export type ResourceLoader = (req: Request) => Promise<

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] api-design

ResourceLoader returns loosely typed { createdBy?: string; lifecycleStage?: string } not backed by any shared type in boost-common. Acceptable for placeholder scaffolding.

Suggested fix: Define shared interface in boost-common when resource loaders are implemented.

…oint, and security middleware

Implements the core boost-backend plugin package:

- ProviderManager: manages registered AI providers with hot-swap support
  (registerProvider, getActiveProvider, switchProvider)
- boostProviderExtensionPoint: extension point in boost-node for provider
  modules to register their implementations
- boostAiProviderServiceFactory: default service factory resolving to the
  active provider via ProviderManager
- authorizeLifecycleAction: Express middleware implementing fine-grained
  permission check -> boost.admin fallback -> 403 pattern
- validateSecurityMode: rejects 'none' with clear error, warns when
  development-only-no-auth is used in production (NODE_ENV=production)
- Permission registration: all 23 boost permissions registered via
  permissionsRegistry.addPermissions()
- Resource loader placeholders for agents and tools (store integration
  deferred to later issues)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gabemontero gabemontero force-pushed the feat/3298-boost-backend-scaffold branch from 5eaeee2 to be2f52a Compare June 19, 2026 13:38
@sonarqubecloud

Copy link
Copy Markdown

@rhdh-qodo-merge

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Workspace boost, CI step for node 24

Failed stage: check for missing repo fixes [❌]

Failed test name: ""

Failure summary:

The action failed during the yarn fix --check step because Yarn detected the repo is not in the
expected “fixed/synchronized” state.
- yarn fix --check reported: “The following packages are out of
sync, run yarn fix to fix them: @red-hat-developer-hub/backstage-plugin-boost-backend” (lines
316-318).
- This caused the command to exit with code 1, which failed the GitHub Action (line 318).

- Earlier Yarn warnings about unmet peer dependencies (e.g., missing webpack, prettier version
mismatch, missing @typescript-eslint/parser) were warnings and did not directly stop the install,
but may be related to the package being considered “out of sync”.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

251:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-backend�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-backend�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mp405fc5�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
252:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-common�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-common [c2cab]�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mp8e1e5a�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
253:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-common�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-common�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mpe50ee4�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
254:  �[93m➤�[39m YN0002: │ �[38;5;166m@red-hat-developer-hub/�[39m�[38;5;173mbackstage-plugin-boost-node�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:plugins/boost-node�[39m doesn't provide �[38;5;173mwebpack�[39m (�[38;5;111mp456cbf�[39m), requested by �[38;5;166m@backstage/�[39m�[38;5;173mcli�[39m.
255:  �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by your project; run �[38;5;111myarn explain peer-requirements <hash>�[39m for details, where �[38;5;111m<hash>�[39m is the six-letter p-prefixed code.
256:  �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by dependencies; run �[38;5;111myarn explain peer-requirements�[39m for details.
257:  ##[endgroup]
258:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed
259:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Fetch step
260:  ##[group]Fetch step
261:  �[94m➤�[39m YN0013: │ �[38;5;220m1674�[39m packages were added to the project (�[38;5;160m+ 516.03 MiB�[39m).
262:  ##[endgroup]
263:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 7s 399ms
264:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Link step
265:  ##[group]Link step
266:  �[94m➤�[39m YN0007: │ �[38;5;166m@fission-ai/�[39m�[38;5;173mopenspec�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.4.1�[39m must be built because it never has been before or the last one failed
267:  �[94m➤�[39m YN0007: │ �[38;5;166m@swc/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.15.40 [486b9]�[39m must be built because it never has been before or the last one failed
268:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.25.12�[39m must be built because it never has been before or the last one failed
269:  �[94m➤�[39m YN0007: │ �[38;5;166m@nestjs/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:11.1.21 [26b97]�[39m must be built because it never has been before or the last one failed
270:  �[94m➤�[39m YN0007: │ �[38;5;166m@openapitools/�[39m�[38;5;173mopenapi-generator-cli�[39m�[38;5;111m@�[39m�[38;5;111mnpm:2.34.0�[39m must be built because it never has been before or the last one failed
271:  �[94m➤�[39m YN0007: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m must be built because it never has been before or the last one failed
272:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0000: Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled.
...

280:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Post-resolution validation
281:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Post-resolution validation
282:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0060: │ �[38;5;173mprettier�[39m is listed by your project with version �[38;5;111m3.7.4�[39m (�[38;5;111mpc2ecd8�[39m), which doesn't satisfy what �[38;5;166m@spotify/�[39m�[38;5;173mprettier-config�[39m and other dependencies request (�[38;5;37m^2.0.0�[39m).
283:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0002: │ �[38;5;166m@redhat-developer/�[39m�[38;5;173mrhdh-plugins�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m doesn't provide �[38;5;166m@typescript-eslint/�[39m�[38;5;173mparser�[39m (�[38;5;111mp8d7c5c�[39m), requested by �[38;5;166m@spotify/�[39m�[38;5;173meslint-plugin�[39m.
284:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by your project; run �[38;5;111myarn explain peer-requirements <hash>�[39m for details, where �[38;5;111m<hash>�[39m is the six-letter p-prefixed code.
285:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0086: │ Some peer dependencies are incorrectly met by dependencies; run �[38;5;111myarn explain peer-requirements�[39m for details.
286:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
287:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: └ Completed
288:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Fetch step
289:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Fetch step
290:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0013: │ �[38;5;220m690�[39m packages were added to the project (�[38;5;160m+ 288.05 MiB�[39m).
291:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
292:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 4s 365ms
293:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m �[90mYN0000�[39m: ┌ Link step
294:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::group::Link step
295:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.21.5�[39m must be built because it never has been before or the last one failed
296:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;166m@swc/�[39m�[38;5;173mcore�[39m�[38;5;111m@�[39m�[38;5;111mnpm:1.4.13 [366d3]�[39m must be built because it never has been before or the last one failed
297:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.23.1�[39m must be built because it never has been before or the last one failed
298:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.20.2�[39m must be built because it never has been before or the last one failed
299:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;173mcore-js-pure�[39m�[38;5;111m@�[39m�[38;5;111mnpm:3.36.1�[39m must be built because it never has been before or the last one failed
300:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[94m➤�[39m YN0007: │ �[38;5;166m@redhat-developer/�[39m�[38;5;173mrhdh-plugins�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m must be built because it never has been before or the last one failed
301:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m ::endgroup::
...

303:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;166m@internal/�[39m�[38;5;173mboost�[39m�[38;5;111m@�[39m�[38;5;111mworkspace:.�[39m �[32mSTDOUT�[39m �[93m➤�[39m YN0000: · Done with warnings in 19s 47ms
304:  ##[endgroup]
305:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 29s 631ms
306:  �[93m➤�[39m YN0000: · Done with warnings in 45s 580ms
307:  ##[group]Run yarn fix --check
308:  �[36;1myarn fix --check�[0m
309:  shell: /usr/bin/bash -e {0}
310:  env:
311:  CI: true
312:  NODE_OPTIONS: --max-old-space-size=8192
313:  NPM_CONFIG_USERCONFIG: /home/runner/work/_temp/.npmrc
314:  NODE_AUTH_TOKEN: XXXXX-XXXXX-XXXXX-XXXXX
315:  ##[endgroup]
316:  The following packages are out of sync, run 'yarn fix' to fix them:
317:  @red-hat-developer-hub/backstage-plugin-boost-backend
318:  ##[error]Process completed with exit code 1.
319:  Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:40 PM UTC · Completed 1:55 PM UTC
Commit: 7ccaff1 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

* Resolves to the active provider from the `ProviderManager`.
* Install this alongside the plugin in the backend:
*
* ```ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] logic-error

The boostAiProviderServiceFactory calls providerManager.getActiveProvider() eagerly inside factory(), which executes once at service-resolution time. If no provider module has registered yet when the factory resolves, getActiveProvider() throws. The factory returns a fixed reference — subsequent switchProvider() calls won't affect consumers that already resolved the service.

Suggested fix: Change the factory to return a lazy wrapper/proxy that delegates chat, chatStream, and descriptor to providerManager.getActiveProvider() at invocation time rather than resolution time.

*/
export function validateSecurityMode(
mode: string | undefined,
logger: LoggerService,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] fail-open

When boost.security.mode is absent from configuration, validateSecurityMode defaults to 'development-only-no-auth'. The security mode is currently validated and logged but not consumed by downstream logic, making this a latent design concern rather than an active vulnerability. However, defaulting to the least restrictive mode is a fail-open pattern.

Suggested fix: Default to the most restrictive mode ('full') when no mode is configured, or require explicit opt-in for development-only-no-auth.

boostProviderExtensionPoint,
} from '@red-hat-developer-hub/backstage-plugin-boost-node';
import { Router } from 'express';
import { ProviderManager } from './provider/ProviderManager';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] api-contract

Module-level singleton ProviderManager couples the factory to the plugin module's import graph. If imported from different module instances, they'd operate on different ProviderManager instances. Deviates from Backstage DI patterns where state is created inside register().

Suggested fix: Move new ProviderManager() inside the register() function body and wire through Backstage's DI.

}

// Set up HTTP routes
const router = Router();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] security-mode-unused

The validated securityMode value is computed and logged but never consumed by any downstream logic. Expected for a scaffold PR.

return async (req: Request, _res: Response, next: NextFunction) => {
try {
const credentials = await httpAuth.credentials(req);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] auth-bypass

The authorizeLifecycleAction middleware falls back to boost.admin when a fine-grained permission is denied. While documented as intentional, admin holders implicitly bypass all fine-grained permissions including future separation-of-duties controls.

register(env) {
// Register the extension point so provider modules can register
env.registerExtensionPoint(boostProviderExtensionPoint, {
registerProvider(provider) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] unused-dependency

httpAuth declared in deps but not destructured; permissions destructured as _permissions but unused.

* the active provider automatically.
*/
registerProvider(provider: AgenticProvider): void {
const id = provider.descriptor.id;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] race-condition

No synchronization in ProviderManager, though safe in Node.js single-threaded synchronous context.

getActiveProvider(): AgenticProvider {
if (!this.activeProvider) {
throw new Error(
'No AI provider is registered. Install a provider module ' +

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling-pattern

Uses plain Error instead of @backstage/errors classes (NotFoundError, ConflictError), unlike security.ts which uses NotAllowedError.

* @packageDocumentation
*/

export {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] export-style

Exports more items than typical for backend plugins in this repo. Exporting ProviderManager as a public class creates a larger contract surface.

* limitations under the License.
*/

import type { Request, Response, NextFunction, RequestHandler } from 'express';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] type-safety

authorizeLifecycleAction accepts BasicPermission but some boost permissions are ResourcePermission. TypeScript prevents misuse at compile time.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

boost-backend — Core plugin scaffold, ProviderManager, and security middleware (issue 2 of 15)

0 participants