Skip to content

feat(#3597): skills marketplace route improvements and runtime config#3599

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/3597-skills-routes-improvements
Open

feat(#3597): skills marketplace route improvements and runtime config#3599
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/3597-skills-routes-improvements

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Refactor skills marketplace routes per openspec section 8 tasks:

  • 8b.1: Add boost.skillsMarketplace.runtimes[] Zod schema to
    schemas.ts with yaml-only scope defining runtime fields (id,
    name, description, image, language, footprint, features, status)
  • 8b.2: Refactor GET /skills/runtimes to read from local app-config
    instead of proxying to external catalog
  • 8c.1: Change POST /skills/deploy to accept runtimeId instead of
    ociImage; resolve container image from configured runtimes
  • 8c.2: Extract manifest generation into manifestBuilder.ts with
    buildDeploymentManifest() and validateRfc1123Label() functions
  • 8c.3: Add manifestBuilder unit tests and update deploy tests for
    runtimeId resolution
  • 8a.3: Add proxy tests for GET /skills and GET /skills/domains
    covering URL construction, query param forwarding, feature gate,
    permission checks, and non-JSON upstream handling

Additional improvements from issue review section:

  • Add boostAdminPermission fallback to requireAccess middleware
    matching the pattern in chat/routes.ts and mcp/routes.ts
  • Add AbortSignal.timeout() to proxyToSkillsCatalog fetch calls
  • Validate skillId and deployment name against K8s RFC 1123 rules
  • Accept separate resources.requests and resources.limits in deploy

Closes #3597

Post-script verification

  • Branch is not main/master (agent/3597-skills-routes-improvements)
  • Secret scan passed (gitleaks — d196266107606e76ba67a676678bc15c6972d338..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Refactor skills marketplace routes per openspec section 8 tasks:

- 8b.1: Add boost.skillsMarketplace.runtimes[] Zod schema to
  schemas.ts with yaml-only scope defining runtime fields (id,
  name, description, image, language, footprint, features, status)
- 8b.2: Refactor GET /skills/runtimes to read from local app-config
  instead of proxying to external catalog
- 8c.1: Change POST /skills/deploy to accept runtimeId instead of
  ociImage; resolve container image from configured runtimes
- 8c.2: Extract manifest generation into manifestBuilder.ts with
  buildDeploymentManifest() and validateRfc1123Label() functions
- 8c.3: Add manifestBuilder unit tests and update deploy tests for
  runtimeId resolution
- 8a.3: Add proxy tests for GET /skills and GET /skills/domains
  covering URL construction, query param forwarding, feature gate,
  permission checks, and non-JSON upstream handling

Additional improvements from issue review section:
- Add boostAdminPermission fallback to requireAccess middleware
  matching the pattern in chat/routes.ts and mcp/routes.ts
- Add AbortSignal.timeout() to proxyToSkillsCatalog fetch calls
- Validate skillId and deployment name against K8s RFC 1123 rules
- Accept separate resources.requests and resources.limits in deploy

Closes #3597
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 26, 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

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

@codecov

codecov Bot commented Jun 26, 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.

@sonarqubecloud

Copy link
Copy Markdown

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:36 AM UTC · Completed 2:47 AM UTC
Commit: d196266 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

High

  • [error-handling] workspaces/boost/plugins/boost-backend/src/skills/manifestBuilder.ts:70validateRfc1123Label throws a plain Error, but the Backstage Express error handler maps error types by name: InputError → 400, NotAllowedError → 403, NotFoundError → 404, and everything else → 500. Since this function throws a generic Error, invalid user input (bad skillId or deployment name) surfaces as HTTP 500 Internal Server Error instead of HTTP 400 Bad Request. The test at routes.test.ts explicitly asserts expect(res.status).toBe(500) for an RFC 1123 violation, confirming the wrong behavior is encoded as expected.
    Remediation: Change validateRfc1123Label to throw InputError from @backstage/errors instead of a plain Error. Update the test assertion from toBe(500) to toBe(400).

Medium

  • [logic-error] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — The deploymentId is constructed as `skill-${skillId}-${Date.now()}` and used as a Kubernetes label value for boost.redhat.com/deployment-id. K8s label values must be at most 63 characters. With a valid skillId of up to 63 characters, deploymentId reaches ~83 characters (6 prefix + 63 skillId + 1 separator + 13 timestamp), exceeding the limit and causing K8s API rejection.
    Remediation: Truncate skillId within deploymentId to keep the total under 63 characters, or use a hash/shorter unique suffix.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:338 — The namespace parameter from req.body flows directly into the K8s Deployment manifest without RFC 1123 validation. Both skillId and name are validated via validateRfc1123Label, but namespace is not — an inconsistency in validation coverage.
    Remediation: Add validateRfc1123Label(deploymentNamespace, 'namespace') after the namespace is resolved.

Low

  • [error-handling] workspaces/boost/plugins/boost-backend/src/skills/routes.ts — The AbortSignal.timeout(PROXY_TIMEOUT_MS) added to proxyToSkillsCatalog's fetch call throws a TimeoutError when the upstream is slow. This is not caught specifically and propagates as a generic 500. A 504 Gateway Timeout would be more semantically correct.

  • [test-integrity] workspaces/boost/plugins/boost-backend/src/skills/routes.test.ts — The mockFetch is installed on global.fetch in beforeAll but never restored in afterAll. While Jest typically isolates test files in separate workers, restoring in afterAll is good test hygiene.

  • [permission-scope-change] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:145 — The requireAccess middleware now grants access to users with boostAdminPermission as a fallback. This widens the set of identities that can access read endpoints. The change is consistent with the pattern used in chat/routes.ts and chat/conversationRoutes.ts.

  • [data-exposure] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:312 — The GET /skills/runtimes endpoint returns the full SkillRuntime object including the image field (OCI registry URL). The config description states "frontends never see registry URLs" but the endpoint exposes them. See also: [input-validation] finding at this location.

  • [input-validation] workspaces/boost/plugins/boost-backend/src/skills/routes.ts:338 — The resources object from req.body contains arbitrary user-supplied strings placed directly into the K8s manifest without format validation. K8s itself validates resource quantities, providing a secondary defense layer.


Labels: PR modifies boost workspace backend plugin with feature-level route and schema changes.

@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.

* @public
*/
export function validateRfc1123Label(value: string, fieldName: string): void {
if (!RFC_1123_LABEL_RE.test(value)) {

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] error-handling

validateRfc1123Label throws a plain Error, but the Backstage Express error handler maps error types by name: InputError -> 400, NotAllowedError -> 403, NotFoundError -> 404, and everything else -> 500. Since this function throws a generic Error, invalid user input (bad skillId or deployment name) surfaces as HTTP 500 Internal Server Error instead of HTTP 400 Bad Request. The test at routes.test.ts explicitly asserts expect(res.status).toBe(500) for an RFC 1123 violation, confirming the wrong behavior is encoded as expected.

Suggested fix: Change validateRfc1123Label to throw InputError from @backstage/errors instead of a plain Error. Update the test assertion from toBe(500) to toBe(400).

// Resolve container image from configured runtimes
const runtimes = getRuntimes();
const runtime = runtimes.find(r => r.id === runtimeId);
if (!runtime) {

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] input-validation

The namespace parameter from req.body flows directly into the K8s Deployment manifest without RFC 1123 validation. Both skillId and name are validated via validateRfc1123Label, but namespace is not.

Suggested fix: Add validateRfc1123Label(deploymentNamespace, 'namespace') after the namespace is resolved.

@@ -96,10 +145,22 @@ export function createSkillsRoutes(options: SkillsRoutesOptions): Router {
[{ permission: boostAccessPermission }],

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] permission-scope-change

The requireAccess middleware now grants access to users with boostAdminPermission as a fallback when boostAccessPermission is denied. This widens the set of identities that can access read endpoints. The change is consistent with the same pattern used in chat/routes.ts and chat/conversationRoutes.ts.

requireSkillsEnabled,
requireAdmin,
async (req, res, next) => {
try {

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] data-exposure

The GET /skills/runtimes endpoint returns the full SkillRuntime object including the image field (OCI container registry URL) to any user with boost access permission. The config field description states 'The backend resolves runtimeId to an image server-side so frontends never see registry URLs,' but the runtimes endpoint exposes these URLs.

Suggested fix: Strip the image field from the runtime objects returned by the GET /skills/runtimes endpoint.

// Resolve container image from configured runtimes
const runtimes = getRuntimes();
const runtime = runtimes.find(r => r.id === runtimeId);
if (!runtime) {

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] input-validation

The resources object from req.body contains arbitrary user-supplied strings placed directly into the K8s Deployment manifest without format validation.

@fullsend-ai-review fullsend-ai-review Bot added workspace/boost Boost workspace (Backstage AI plugin) enhancement New feature or request labels Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request workspace/boost Boost workspace (Backstage AI plugin)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

boost-skills-routes: Skills marketplace route improvements and runtime config (issue 16 after 15)

0 participants