fix(pipeline): decouple Runner from extension permissions (LANG-54) (#2723)

* fix(pipeline): decouple runner from extension allowlist

Co-authored-by: multica-agent <[email protected]>

* fix(pipeline): complete runner diagnostics and real runtime coverage

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
This commit is contained in:
RockChinQ
2026-10-19 23:07:11 +08:00
committed by GitHub
co-authored by multica-agent
parent 5a6ddbff6f
commit d9e4e7d9df
24 changed files with 1012 additions and 35 deletions
+5 -8
View File
@@ -14,7 +14,7 @@ from ...api.http.context import ExecutionContext
from ...api.http.service.tenant import TenantContext
from .descriptor import RunnerDescriptor
from .id import parse_runner_id, format_runner_id
from .errors import RunnerNotFoundError, RunnerNotAuthorizedError
from .errors import RunnerNotFoundError
class RunnerRegistry:
@@ -229,14 +229,13 @@ class RunnerRegistry:
Args:
runner_id: Runner ID to lookup
bound_plugins: Optional bound plugins filter
bound_plugins: Legacy pipeline extension filter; does not authorize Runners.
Returns:
RunnerDescriptor
Raises:
RunnerNotFoundError: If runner not found
RunnerNotAuthorizedError: If runner not in bound plugins
"""
# Parse and validate runner ID format
try:
@@ -259,11 +258,9 @@ class RunnerRegistry:
if descriptor is None:
raise RunnerNotFoundError(runner_id)
# Check authorization
if bound_plugins is not None:
plugin_id = descriptor.get_plugin_id()
if plugin_id not in bound_plugins:
raise RunnerNotAuthorizedError(runner_id, bound_plugins)
# A selected Runner is independent of the pipeline extension allowlist.
# Installation enablement and Workspace scope are enforced by the connector
# again at invocation; never widen the extension permissions on the query.
return descriptor
+2 -1
View File
@@ -189,7 +189,8 @@ class PipelineService:
pipeline_data['stages'] = default_stage_order.copy()
pipeline_data['is_default'] = default
pipeline_data['config'] = await self.get_default_pipeline_config(context)
if 'config' not in pipeline_data:
pipeline_data['config'] = await self.get_default_pipeline_config(context)
RunnerConfigResolver.validate_pipeline_config(pipeline_data['config'])
# Ensure extensions_preferences is set with enable_all_plugins and enable_all_mcp_servers=True by default
+9 -2
View File
@@ -132,7 +132,13 @@ class LangBotMCPServer:
await ap.pipeline_service.get_pipeline_extensions(_authorized(Permission.RESOURCE_VIEW), pipeline_uuid)
)
@mcp.tool(description='Replace all Pipeline extension bindings. All binding lists and switches are required.')
@mcp.tool(
description=(
'Replace all Pipeline extension bindings. All binding lists and switches are required. '
'Plugin switches control EventListener, Tool and Command only, not the independently selected Runner. '
'Do not add a Runner plugin here just to run it; installation, global enablement and Workspace access still apply.'
)
)
async def update_pipeline_extensions(
pipeline_uuid: str,
bound_plugins: list[dict],
@@ -394,7 +400,8 @@ class LangBotMCPServer:
@mcp.tool(
description=(
'Create a pipeline. `pipeline_data` matches the LangBot POST '
'/api/v1/pipelines body. Returns the new pipeline UUID.'
'/api/v1/pipelines body. Supplied config and extension preferences are preserved; '
'omitting config uses installed Runner defaults. Returns the new pipeline UUID.'
)
)
async def create_pipeline(pipeline_data: dict) -> str: