{"record":{"id":"1b6907657052629f","repo":"HKUDS/nanobot","slug":"channel-plugin-webui-entry-must-stay-inside-its-pa","errorCode":null,"errorMessage":"channel plugin webui entry must stay inside its package","messagePattern":"channel plugin webui entry must stay inside its package","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"nanobot/channels/plugin.py","lineNumber":71,"sourceCode":"            raise TypeError(\"channel plugin setup must be a ChannelSetupSpec or None\")\n        if not isinstance(cast(object, self.management), ChannelManagementSpec):\n            raise TypeError(\"channel plugin management must be a ChannelManagementSpec\")\n        if not isinstance(cast(object, self.dependencies), tuple) or not all(\n            isinstance(cast(object, requirement), str) and requirement.strip()\n            for requirement in self.dependencies\n        ):\n            raise TypeError(\"channel plugin dependencies must be a tuple of requirements\")\n        for dependency in self.dependencies:\n            try:\n                Requirement(dependency)\n            except InvalidRequirement as exc:\n                raise ValueError(\n                    f\"channel plugin dependency is not a valid requirement: {dependency}\"\n                ) from exc\n        if self.webui is not None:\n            webui = self.webui.replace(\"\\\\\", \"/\")\n            if webui.startswith(\"/\") or \"..\" in webui.split(\"/\"):\n                raise ValueError(\"channel plugin webui entry must stay inside its package\")\n            object.__setattr__(self, \"webui\", webui)\n\n    def load_channel_class(self) -> type[BaseChannel]:\n        \"\"\"Resolve and validate the runtime class only when the channel is needed.\"\"\"\n        from nanobot.channels.base import BaseChannel\n\n        module_name, _, attr_name = self.runtime.partition(\":\")\n        module = importlib.import_module(module_name)\n        channel_cls: Any = getattr(module, attr_name, None)\n        if (\n            not isinstance(channel_cls, type)\n            or not issubclass(channel_cls, BaseChannel)\n            or channel_cls is BaseChannel\n        ):\n            raise ImportError(\n                f\"Channel plugin '{self.name}' runtime '{self.runtime}' \"\n                \"does not resolve to a BaseChannel subclass\"\n            )","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/HKUDS/nanobot/blob/42f37dc4c0e409eac7818a82d165c5f2757bc075/nanobot/channels/plugin.py#L53-L89","documentation":"Path-safety validation in ChannelPlugin.__post_init__: the webui entry, after normalizing backslashes to forward slashes, is absolute (leading '/') or contains '..' path segments, so it would escape the channel plugin's package directory. The guard keeps the WebUI entry file inside the plugin package.","triggerScenarios":"Thrown at nanobot/channels/plugin.py:71 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use a relative path within the channel package, e.g. 'webui/index.html'","Remove leading slashes and any '..' segments from the webui entry"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"42f37dc4c0e409eac7818a82d165c5f2757bc075","analyzedAt":"2026-08-26T00:18:52.276Z","contentChangedAt":"2026-08-26T00:18:52.276Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}