canship 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README-zh-CN.md +99 -153
  2. package/README.md +97 -151
  3. package/dist/cli.js +3156 -542
  4. package/dist/index.js +2434 -195
  5. package/package.json +1 -1
package/README-zh-CN.md CHANGED
@@ -1,127 +1,109 @@
1
1
  # canship
2
2
 
3
- 面向 JavaScript / TypeScript 项目的本地静态扫描器,检测凭据暴露与访问控制配置错误。扫描不执行项目代码、不上传文件、不联网。
3
+ 面向 JavaScript / TypeScript Web 应用的本地静态扫描器,检查凭据暴露、访问控制配置及请求输入风险。扫描不执行项目代码、不上传文件、不联网。
4
4
 
5
5
  [English](./README.md)
6
6
 
7
+ > 本文对应 `0.6.0`。使用 `npx canship --version` 确认已安装版本。
8
+
7
9
  ## 快速开始
8
10
 
9
11
  ```powershell
10
- npx canship .
12
+ npx canship
11
13
  ```
12
14
 
13
- 要求 Node.js ≥18,无运行时依赖。安装软件包可能联网;Git 检查仅使用本地历史。仓库中无法调用 Git 时,扫描标记为未完成。
15
+ 默认扫描当前目录,也可指定路径。要求 Node.js ≥18,无运行时依赖,安装可能联网。Git 检查覆盖本地跟踪文件及提交历史,不访问远程仓库;历史无法读取时标记扫描不完整。
16
+
17
+ 以下报告来自发布前开发构建,使用示例数据。
14
18
 
15
- > 本文对应 0.4.0,较早的 [npm 版本](https://www.npmjs.com/package/canship) 可能不包含下述全部功能。
19
+ ![终端报告](https://raw.githubusercontent.com/Tasomei/canship/main/docs/images/terminal.png)
16
20
 
17
21
  ## 检测范围
18
22
 
19
- | 检查项 | 级别 |
20
- |---|---|
21
- | 硬编码凭据、私钥及含密码的数据库连接串 | P0 |
22
- | 公开环境变量中的私密值 | P0 |
23
- | 源码或公开环境变量中的 Supabase 管理员凭据 | P0 |
24
- | Git 跟踪及历史 `.env` 文件中的凭据或疑似私密值 | P0 |
25
- | Supabase 迁移中未启用行级安全(RLS)的表 | P1 |
26
- | Supabase 条件恒为真的 RLS 策略 | P1 |
27
- | 内容可被列举的 Supabase 公开存储桶 | P2 |
28
- | Firebase 无条件访问及固定日期测试规则(Firestore、Storage、Realtime Database) | P1 |
29
- | 服务端数据操作未识别到鉴权 | P0 / P1 |
30
- | 携带凭据的 CORS 来源回显或通配符配置 | P1 / P2 |
31
-
32
- 识别 OpenAI、Anthropic、AWS、Stripe、GitHub、npm 等凭据格式及常见前端公开环境变量前缀。规则 ID、范围与限制见 `--list-rules`。
33
-
34
- ### 鉴权检查范围
35
-
36
- | 框架 | 检查入口 |
37
- |---|---|
38
- | Next.js | `app/` 下的 route 处理函数、Pages Router `/api`、`'use server'` 函数 |
39
- | SvelteKit | `+server` 端点及 `+page.server` 表单 action |
40
- | Nuxt | `server/api`、`server/routes` |
41
- | Remix / React Router | `app/routes` 中导出的 `loader`、`action` |
42
- | Astro | `src/pages` 中的端点 |
23
+ | 类别 | 级别 | 范围 |
24
+ |---|:---:|---|
25
+ | 凭据 | `P0` | 硬编码密钥、私钥、含密码的数据库连接串、公开变量中的私密值、Supabase 管理员密钥、Git 跟踪或历史中的非模板 `.env` 文件 |
26
+ | API 访问 | `P0/P1` | 未识别到鉴权的数据库操作、服务端信任 Supabase `getSession()`、未验证的 Stripe webhook |
27
+ | 数据库规则 | `P1/P2` | Supabase 表未启用 RLS、无条件放行策略、允许公开列出对象的存储桶;Firebase 开放规则及限时测试规则 |
28
+ | CORS | `P1/P2` | 携带凭据的来源回显或通配符配置 |
29
+ | 请求输入 | `P1/P2` | 请求输入参与 SQL 或命令构造、调用方可控的请求主机和重定向目标 |
43
30
 
44
- 支持路由组和工作区应用,不检查 SvelteKit 页面 load 与 remote function。已识别的 Next.js、Astro 中间件鉴权可抑制覆盖路由的结果;Server Function 需在函数内鉴权。SvelteKit hooks、Nuxt 中间件及本地鉴权函数可降低置信度,但不抑制结果。
31
+ 凭据格式包括 OpenAI、Anthropic、AWS、Stripe、GitHub、npm 等。Firebase 覆盖 Firestore、Storage、Realtime Database。`--list-rules` 列出规则 ID、范围及局限。
45
32
 
46
- Supabase 检查重放本地迁移并读取支持的存储桶配置,不检查仅在控制台修改的配置或省略子句隐含的策略条件。
33
+ ### 服务端入口
47
34
 
48
- ### 置信度与证据
35
+ | 框架 | 入口 |
36
+ |---|---|
37
+ | Next.js | App Router 处理函数、Pages Router `/api`、`'use server'` 函数 |
38
+ | SvelteKit | `+server` 端点、`+page.server` 表单 action |
39
+ | Nuxt | `server/api`、`server/routes` |
40
+ | Remix / React Router | `app/routes` 中的 `loader`、`action` 导出 |
41
+ | Astro | `src/pages` 中的端点 |
49
42
 
50
- `certain`(确定)与 `likely`(疑似)描述静态证据,不验证凭据有效性或线上状态。默认仅展示 `certain`;隐藏的 `likely` 仍影响退出码。
43
+ 已识别的 Next.js/Astro 中间件可抑制覆盖范围内的鉴权结果;Server Function 需在函数内检查。本地辅助函数、SvelteKit hooks、Nuxt 中间件可降低置信度,但保留结果。输入分析追踪可见的赋值、解构和字符串构造,不以辅助函数名称证明安全。
51
44
 
52
- 管理员客户端相关结果附带数据操作、导入和客户端构造位置。支持限定语法内的 Supabase 构造器别名、本地鉴权导入、重导出及返回函数的封装。鉴权解析最多 8 跳,证据链最多 24 步,截断时提示。间接鉴权证据仅降低置信度;导入关系不证明运行时数据流。
45
+ 路由分析不覆盖 SvelteKit 页面 load、remote function,以及独立的 Express/Hono/Fastify 处理函数;凭据、CORS 等内容规则仍适用。
53
46
 
54
- ## 命令行
47
+ ## 结果
55
48
 
56
- 省略路径时扫描当前目录。报告正文为英文。
49
+ 报告正文为英文。终端按文件分组,`--verbose` 展开摘录、说明、证据和修复步骤。HTML 为自包含离线报告,支持筛选、分组、人工操作清单及修复提示复制。
57
50
 
58
- | 参数 | 作用 |
59
- |---|---|
60
- | `-a`, `--all` | 所有格式包含 `likely` 结果 |
61
- | `--json` | 输出 JSON |
62
- | `--fix-prompt` | 输出修复指令及独立的人工操作清单 |
63
- | `--report[=file]` | 写入 HTML,默认 `canship-report.html` |
64
- | `--sarif[=file]` | 写入 SARIF 2.1.0,默认 `canship.sarif` |
65
- | `--no-excerpts` | 省略源码摘录,不改变结果和退出码 |
66
- | `--changed-since=ref` | 按变更文件筛选报告,不改变扫描范围和退出码 |
67
- | `--only=ids` / `--skip=ids` | 选择或排除规则,支持逗号分隔及重复参数 |
68
- | `--list-rules` | 列出规则,不扫描;支持 `--json` |
69
- | `--baseline[=file]` | 抑制基线结果,默认 `canship-baseline.json` |
70
- | `--baseline-write[=file]` | 记录结果后退出,默认路径同上 |
71
- | `--no-config` | 忽略项目配置 |
72
- | `--no-ignore-markers` | 不遵从源码忽略标记 |
73
- | `--best-effort` | 无结果时,允许不完整扫描退出 `0` |
74
- | `-h`, `--help` / `-v`, `--version` | 显示帮助或版本 |
51
+ ![HTML 报告](https://raw.githubusercontent.com/Tasomei/canship/main/docs/images/report.png)
75
52
 
76
- `--json` 与 `--fix-prompt` 互斥;HTML、SARIF 可与任一模式组合。
77
-
78
- ### 退出码
53
+ `certain` 表示静态证据充分,`likely` 需人工审阅;测试和示例中的结果降为 `likely`。默认仅展示 `certain`,`--all` 显示全部。置信度仅反映静态证据,不代表凭据有效或风险已在运行时验证。
79
54
 
80
55
  | 退出码 | 含义 |
81
56
  |---|---|
82
- | `0` | 无结果;扫描完整,或由 `--best-effort` 接受不完整状态 |
83
- | `1` | 存在 `certain` 的 P0/P1 结果 |
84
- | `2` | 存在其他结果,包括隐藏的 `likely` |
85
- | `3` | 参数或工具错误,或未被接受的不完整扫描 |
86
-
87
- 统计以规则筛选、忽略标记和基线处理后的结果为准。结果退出码优先于不完整状态;`--best-effort` 不改变 `1` 或 `2`。
88
-
89
- ### 变更文件视图
90
-
91
- `--changed-since=origin/main` 比较本地共同祖先与工作区,包含未被 Git 忽略的新文件,不拉取远程。仍扫描全项目,仅展示主位置或证据位置发生变更的结果;仓库级结果及证据链截断的结果保留。
57
+ | `0` | 无结果,且扫描完整或由 `--best-effort` 接受 |
58
+ | `1` | 至少一条 `certain` 的 P0/P1 结果 |
59
+ | `2` | 其他结果,包括隐藏的 `likely` |
60
+ | `3` | 参数错误、工具错误或未被接受的不完整扫描 |
92
61
 
93
- 隐藏结果仍影响退出码:此功能用于审阅,不是“仅新增问题阻断 CI”的策略。缺少 Git、引用或共同历史时退出 `3`,`--best-effort` 不豁免。不能与 `--baseline-write` 组合。
62
+ 退出码基于规则筛选、忽略注释和基线处理后的结果。有结果时优先于扫描不完整;`--best-effort` 不改变 `1` 或 `2`。
94
63
 
95
- ### 结构化报告
64
+ ## 命令行
96
65
 
97
- JSON 使用 `schemaVersion: 1`,包内附带 [结构定义](./schemas/scan-report-v1.schema.json)。调用方应兼容新增字段,拒绝不支持的结构版本。
66
+ `npx canship [path] [options]`
98
67
 
99
- - `findings`:抑制及展示筛选后的结果。
100
- - `hiddenLikely`、`baselineSuppressed`、`baselineStale`:筛选与基线统计。
101
- - `partial`、`errors`、`skipped`、`filesScanned`:扫描完整性,须独立于退出码检查。
102
- - `changeView`:启用变更视图时的筛选统计及全量扫描统计。
68
+ | 参数 | 作用 |
69
+ |---|---|
70
+ | `-a`、`--all` | 包含 `likely` 结果 |
71
+ | `--verbose` | 展开终端结果 |
72
+ | `--report[=file]` | 写入 HTML,默认 `canship-report.html` |
73
+ | `--open` | 打开 `--report` 输出;CI 和非交互终端中禁用 |
74
+ | `--json` | 输出 JSON |
75
+ | `--sarif[=file]` | 写入 SARIF 2.1.0,默认 `canship.sarif` |
76
+ | `--fix-prompt` | 输出修复指令及独立的人工操作清单 |
77
+ | `--no-excerpts` | 移除所有报告中的摘录 |
78
+ | `--changed-since=ref` | 展示变更文件结果,保留全量扫描退出码 |
79
+ | `--only=ids` / `--skip=ids` | 选择或排除规则及命名空间,逗号分隔,可重复 |
80
+ | `--list-rules` | 列出规则而不扫描,支持 `--json` |
81
+ | `--baseline[=file]` / `--baseline-write[=file]` | 抑制或记录结果,默认 `canship-baseline.json` |
82
+ | `--no-config` / `--no-ignore-markers` | 忽略项目配置或源码抑制注释 |
83
+ | `--best-effort` | 允许没有结果的不完整扫描退出 `0` |
84
+ | `-h`、`--help` / `-v`、`--version` | 显示帮助或版本 |
103
85
 
104
- SARIF 包含执行诊断与证据关联位置。`--list-rules --json` 返回独立的 `kind: "rule-catalog"` 文档。
86
+ `--json` 与 `--fix-prompt` 互斥,均可同时输出 HTML 和 SARIF。
105
87
 
106
- ## 程序化 API
88
+ `--changed-since` 比较本地共同祖先与工作区,包含未被忽略的新文件,不拉取远程、不缩小扫描范围。缺少 Git、引用或共同历史时退出 `3`;不能与 `--baseline-write` 组合。
107
89
 
108
- 提供 Node.js ESM 入口及 TypeScript 类型:
90
+ ## 配置
109
91
 
110
- ```js
111
- import { scan, summarize, listRules } from 'canship'
92
+ `canship.config.json` 支持 `baseline`、`only`、`skip`、`all`。命令行参数优先,`only` 与 `skip` 互斥。
112
93
 
113
- const result = await scan('./my-app', { noExcerpts: true })
114
- console.log(summarize(result))
115
- console.log(listRules())
94
+ ```json
95
+ { "skip": ["cors/wildcard-with-credentials"], "all": false }
116
96
  ```
117
97
 
118
- `scan()` 返回全部置信度结果,支持 `only`、`skip`、`honorIgnoreMarkers`(默认 `true`)、`noExcerpts`(默认 `false`)。不读取项目配置、不应用基线、不写报告、不设置进程退出码。无效参数或根目录抛出异常;扫描缺口保留在结果中。
98
+ 独占行注释 `canship-ignore-file` 排除整个文件;`canship-ignore-next-line [rule]` 抑制下一行,可限定单条规则。报告披露排除项;主动抑制不标记扫描不完整,可能使退出码降为 `0`。扫描不可信项目时使用 `--no-config --no-ignore-markers`。
119
99
 
120
- `summarize()` 返回结果数、阻断数、疑似数、`partial` 及默认 CLI 退出码。`listRules()` 返回独立的规则目录副本。
100
+ 基线表示接受已有结果,不代表问题已修复。v2 格式不受行号移动影响,但凭据变化会重新报告。
101
+
102
+ 默认路径相对扫描目录,显式路径相对工作目录;读取与写入互斥。缺失、无效或 v1 基线退出 `3`。写入成功退出 `0`,不完整或选择性扫描会提示。
121
103
 
122
104
  ## GitHub Action
123
105
 
124
- 保存为 `.github/workflows/canship.yml`。Action 安装指定 npm 版本,扫描检出目录并生成统计摘要;不安装或执行项目依赖,SARIF 需显式启用上传。
106
+ 保存为 `.github/workflows/canship.yml`:
125
107
 
126
108
  ```yaml
127
109
  name: canship
@@ -136,78 +118,56 @@ jobs:
136
118
  with:
137
119
  fetch-depth: 0
138
120
  persist-credentials: false
139
- - uses: Tasomei/canship@b4cbbfe6b5c4c88164b9388d121f7651032259a4
121
+ - uses: Tasomei/canship@97c14d1f1e494a49adf716c455b597edf6ae1d88
140
122
  with:
141
- version: '0.4.0'
123
+ version: '0.6.0'
124
+ honor-ignore-markers: false
142
125
  ```
143
126
 
144
- 提交号固定 Action 实现;`version` 选择 npm 扫描器,不使用仓库源码。该固定提交默认安装 0.3.2;示例显式选择 0.4.0。
127
+ 提交哈希固定 Action 实现,`version` 指定 npm 扫描器版本,不使用开发分支源码。Action 使用 Node.js 22,不安装或运行项目依赖,仅输出统计摘要。
145
128
 
146
- | 输入 | 默认值 | 说明 |
129
+ | 输入 | 默认值 | 含义 |
147
130
  |---|---|---|
148
- | `path` | `.` | 检出目录内的扫描路径 |
149
- | `version` | `0.3.2` | 精确 npm 版本,不接受范围或标签 |
131
+ | `version` | `0.5.0` | 精确 npm 扫描器版本 |
150
132
  | `fail-on` | `blocking` | `blocking`:确定的 P0/P1;`any`:全部结果;`none`:仅报告 |
151
- | `only` / `skip` | 未设置 | 互斥的规则选择器 |
152
- | `baseline` | 未设置 | 相对扫描目录的已有基线 |
153
- | `use-config` | `false` | 启用项目配置 |
154
- | `upload-sarif` | `false` | 上传至 GitHub 代码扫描 |
155
- | `category` | `canship` | 扫描目标的 SARIF 分类 |
156
-
157
- 输出:`exit-code`、`findings`、`blocking`、`partial`。统计包含基线与排除处理后的疑似结果。扫描不完整、工具错误或报告不兼容始终失败,`fail-on: none` 也不例外。
158
-
159
- 上传 SARIF 需 `security-events: write` 及 [代码扫描支持](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/integrate-with-existing-tools/upload-sarif-file),Fork PR 可能权限不足。上传前需审阅报告。不可信 PR 使用 `pull_request`,不要使用 `pull_request_target`。Action 为后续步骤设置 Node.js 22;需要其他版本时使用独立扫描任务。
160
-
161
- ## 配置与基线
162
-
163
- 扫描目录中的 `canship.config.json` 支持 `baseline`、`only`、`skip`、`all`:
164
-
165
- ```json
166
- {
167
- "skip": ["cors/wildcard-with-credentials"],
168
- "all": false
169
- }
170
- ```
171
133
 
172
- 命令行参数优先。`only`、`skip` 互斥,接受规则 ID 或命名空间。`--best-effort` 仅限命令行。扫描不可信项目时使用 `--no-config --no-ignore-markers`。
134
+ 扫描不完整或工具错误始终失败。默认不读取项目配置、不上传 SARIF。输入输出见 [action.yml](./action.yml)。
173
135
 
174
- ### 忽略标记
136
+ 上传 SARIF 需 `security-events: write` 及代码扫描支持,fork PR 可能权限不足;上传前应审阅报告。不可信 PR 使用 `pull_request`,不要使用 `pull_request_target`。
175
137
 
176
- 独占注释行的 `canship-ignore-file` 排除整个文件;`canship-ignore-next-line` 抑制下一行,可限定规则:
138
+ ## API 与结构化输出
177
139
 
178
- ```ts
179
- // canship-ignore-next-line cors/wildcard-with-credentials
180
- const corsOptions = { origin: '*', credentials: true }
181
- ```
182
-
183
- 报告披露排除与抑制信息。主动排除不标记为未完成,可使退出码降为 `0`。`--no-config` 不禁用标记,`--no-ignore-markers` 才会禁用。
184
-
185
- ### 基线
186
-
187
- 记录已有结果:
140
+ ```js
141
+ import { scan, summarize } from 'canship'
188
142
 
189
- ```powershell
190
- npx canship --baseline-write
143
+ const result = await scan('./my-app', { noExcerpts: true })
144
+ console.log(summarize(result))
191
145
  ```
192
146
 
193
- 后续扫描抑制这些结果:
147
+ `scan()` 返回全部置信度结果,支持 `only`、`skip`、`honorIgnoreMarkers`(默认 `true`)、`noExcerpts`(默认 `false`)。不加载配置、不应用基线、不写报告、不设置进程退出码;无效参数抛出异常。`listRules()` 返回规则目录。
194
148
 
195
- ```powershell
196
- npx canship --baseline
197
- ```
149
+ JSON 使用 [schemaVersion 1](./schemas/scan-report-v1.schema.json)。须独立于退出码检查 `partial`、`errors`、`skipped`、`filesScanned`。SARIF 包含证据位置和执行诊断。
198
150
 
199
- 默认路径相对扫描目录,显式路径相对工作目录;读取与写入模式互斥。写入成功退出 `0`,不表示无问题;扫描不完整或启用规则筛选时会提示。
151
+ ## 隐私与限制
200
152
 
201
- 基线格式为 v2,移动行号不改变指纹,替换凭据会改变。缺失、损坏及 v1 基线均退出 `3`。基线不含源码摘录,但包含路径、规则和问题描述,提交前需审阅。
153
+ - 静态检查可能误报或漏报,不验证业务授权、限流、依赖漏洞或线上配置。
154
+ - 脱敏仅覆盖已识别格式,未知敏感值可能保留在摘录中;`--no-excerpts` 可移除摘录。路径、名称和基线描述仍可见。
155
+ - Google/Firebase/Maps 的 `AIza…` 密钥按公开标识符处理,不单凭其值判定泄露。Supabase 检查依据本地迁移及支持的存储桶配置。
156
+ - 评估快照获取和可选的 SARIF 上传可能联网。
157
+ - 不跟随符号链接;嵌套仓库与子模块需单独扫描。范围内跳过项及分析超限标记扫描不完整;默认排除的依赖和构建目录不计为扫描缺口。
202
158
 
203
- ## 隐私与限制
159
+ | 项目 | 上限 |
160
+ |---|---|
161
+ | 文件读取 | 单文件 2 MiB;单次 128 MiB、10,000 个文件,含探测 |
162
+ | 目录遍历 | 50,000 个条目;16 层 |
163
+ | 结果 | 每文件 100 条,优先保留高严重度、高置信度结果 |
164
+ | Git 历史 | 每文件 100 个相关版本;单条命令 30 秒 |
165
+ | 鉴权解析 | 8 跳;每个路由文件 128 个符号 |
166
+ | 身份/控制流 | 值解析 8 步;表达式 4,000 字符;每函数 512 个赋值/区域;区域嵌套 8 层 |
167
+ | 请求输入追踪 | 值解析 8 步;512 个赋值/区域;表达式 4,000 字符;URL 分析 8 层、静态前缀 200 字符 |
168
+ | Supabase 策略/存储桶解析 | 单条语句 4,000 字符 |
204
169
 
205
- - 静态检查可能漏报或将预期配置报为问题,不验证线上行为,不覆盖限流、注入、依赖漏洞或业务授权。无结果不等于安全。
206
- - 脱敏仅覆盖已识别格式。未识别的敏感值可能保留在摘录中;`--no-excerpts` 移除摘录,并设置 JSON `excerptsOmitted`。路径、名称、说明和基线不匿名化。
207
- - Google/Firebase/Maps 的 `AIza...` 密钥按公开标识符处理,不单凭其值判定泄露。
208
- - 读取上限:单文件 2 MiB,单次 128 MiB、10,000 个文件,目录 16 层。每文件跨规则最多 100 条结果,优先保留高严重度、高置信度结果。
209
- - Git 历史每文件最多 100 个相关版本,单条命令超时 30 秒;Supabase 策略及存储桶语句最多解析 4,000 个字符。超限报告扫描未完成。
210
- - 不跟随符号链接;嵌套仓库、子模块需单独扫描。范围内跳过项使扫描未完成,内置排除的依赖和构建目录除外。
170
+ 证据链最多 24 步,截断时提示。
211
171
 
212
172
  ## 开发
213
173
 
@@ -223,26 +183,12 @@ npm run prepublishOnly
223
183
  npm run test:package
224
184
  ```
225
185
 
226
- 新增规则需包含应检出与不应检出的 [夹具](./test/fixtures/)。运行离线 [评估集](./test/fixtures/evaluation/):
227
-
228
186
  ```powershell
229
187
  npm run evaluate
230
188
  ```
231
189
 
232
- [应用快照](./test/evaluation/projects.json) 需先联网获取并校验来源,目标为 Git 仓库外的新目录:
233
-
234
- ```powershell
235
- node scripts/fetch-evaluation-projects.mjs "$env:TEMP/canship-evaluation"
236
- ```
237
-
238
- 随后离线评估:
239
-
240
- ```powershell
241
- npm run evaluate:projects -- "$env:TEMP/canship-evaluation"
242
- ```
243
-
244
- 项目评估使用临时副本,比较原项目及开放、受限测试变体的全部结果,不安装或运行样本依赖。这些测试不衡量真实项目检出率、Git 历史覆盖率或线上行为。
190
+ 新增规则需包含应检出和不应检出的 [夹具](./test/fixtures/)。固定项目评估见 [清单](./test/evaluation/projects.json)、[获取脚本](./scripts/fetch-evaluation-projects.mjs)、[评估器](./scripts/evaluate-projects.ts)。样本通过不代表真实检出率。
245
191
 
246
192
  ## 许可
247
193
 
248
- [MIT](./LICENSE)。Supabase、Firebase 夹具保留 Apache-2.0,Next.js、`cors` 夹具保留 MIT;来源与许可证随夹具保存。
194
+ [MIT](./LICENSE)。Supabase/Firebase 夹具保留 Apache-2.0,Next.js/`cors` 夹具保留 MIT。