---
title: "Shell"
description: "Kasumi 外部链接插件的方法、origin 白名单与平台差异。"
---


> 理解 Shell 插件的外部链接白名单、URL 校验、平台调用和信任边界。

## 模块边界

Shell 插件只把已授权的绝对 HTTP(S) URL 交给系统浏览器。它不下载内容、不返回浏览器页面、
不处理浏览器后续重定向，也不允许 `javascript:`、`file:`、`data:`、`intent:` 或
`content://` 交给系统。

| 层级 | 入口 | 责任 |
| --- | --- | --- |
| 前端 API | `frontend/src/api/shell.ts` | URL 形状守卫 |
| 协议清单 | `protocol/plugins.json` | `shell` 插件和命令归属 |
| Windows 服务 | `ksm_windows_host_services.cpp` | 精确 origin 白名单和 `ShellExecuteW` |
| Android 服务 | `KSMAndroidHostServices.kt` | 精确 origin 白名单和 `ACTION_VIEW` |

## 平台与权限

| 命令 | Windows | Android | 权限 | 返回 |
| --- | --- | --- | --- | --- |
| `shell.openExternal` | 支持 | 支持 | `shell.open` | `{ opened }` |

## Origin 白名单

宿主初始化时配置精确 origin 白名单，默认空白名单并拒绝所有调用。比较包含 scheme、host
和显式端口；默认端口会规范化。以下输入会被拒绝：

- 带用户名或密码的 URL。
- 相对 URL、空主机或超长 URL。
- 非 HTTP(S) scheme。
- origin 不在宿主白名单中的 URL。

只校验调用时传入的 URL。系统浏览器后续重定向不经过 Kasumi，因此应用必须选择可信目标
origin。

## 示例

```ts
try {
  const result = await KSM.shell.openExternal({
    url: 'https://example.com/docs'
  });

  if (!result.opened) {
    console.warn('系统没有可用浏览器处理器');
  }
} catch (error) {
  if (error instanceof Error && error.name === 'PERMISSION_DENIED') {
    console.warn('origin 未加入宿主白名单');
  }
}
```

## 平台差异

Windows 使用 `ShellExecuteW`；Android 使用 `ACTION_VIEW`。两端服务层都会再次校验精确
origin。Android 不会把 `intent://` 或任意 `content://` 交给系统。

## 错误行为

| 错误码 | 场景 |
| --- | --- |
| `INVALID_ARGS` | scheme、相对 URL、凭据、空主机或长度非法 |
| `PERMISSION_DENIED` | 未授予权限或 origin 不在白名单 |
| `IO_ERROR` | 系统调用失败 |
| `NOT_SUPPORTED` | 宿主没有 Shell 服务 |
| `TIMEOUT` / `CANCELLED` | 请求超时或窗口销毁 |

没有可用处理器时可能返回 `{ opened: false }`，业务应提供可读提示，而不是循环重试。

## 排查路径

1. 确认 URL 是绝对 HTTP(S)，没有用户名密码。
2. 检查宿主白名单是否包含完整 scheme、host 和端口。
3. 确认当前窗口拥有 `shell.open`。
4. `opened: false` 时检查系统默认浏览器或 URL 处理器。
5. 不要把浏览器重定向后的地址当成 Kasumi 已验证的 URL。
6. WebView 外部导航应直接拦截，并显式调用本插件，而不是自动启动系统 Activity。
