MMT40/of-cors
GitHub: MMT40/of-cors
一个无副作用的 TypeScript CORS 诊断库,通过分析请求和响应头来解释浏览器的跨域决策并提供可操作的修复建议。
Stars: 1 | Forks: 0
of-cors
CORS 决策,一目了然。
描述一个浏览器请求及其 CORS 响应头,即可获得带有可操作诊断信息的类型化决策结果——全程无需发起网络请求。
`of-cors` 是一个无副作用的 TypeScript 库,适用于 Node.js、现代浏览器和边缘运行时(edge runtimes)。调用方提供请求和响应信息;该库会解释相应的 CORS 决策,且绝不会更改服务器配置。 ## 为什么选择 of-cors? - **结果具有可操作性:** 提供稳定的问题代码、严重程度、简洁明了的消息以及实用的修复建议。 - **默认类型化:** 提供清晰的 TypeScript 输入和不可变的结果类型。 - **运行时中立:** 提供 ESM 和 CommonJS 构建版本,没有运行时依赖或 Node 专属 API。 - **输入明确:** 无需发起真实请求,即可分析已捕获或假设的响应头。 ## 安装说明 ``` npm install of-cors ``` 支持 ESM 和 CommonJS 消费者: ``` import { analyzeCors } from 'of-cors'; ``` ``` const { analyzeCors } = require('of-cors'); ``` ## 快速开始 ``` import { analyzeCors } from 'of-cors'; const result = analyzeCors({ request: { origin: 'https://app.example.com', url: 'https://api.example.com/users', method: 'GET', }, response: { status: 200, headers: { 'Access-Control-Allow-Origin': 'https://app.example.com', }, }, }); result.allowed; // true result.stage; // "response" result.issues; // [] ``` ## 失败请求诊断 ``` const result = analyzeCors({ request: { origin: 'https://app.example.com', url: 'https://api.example.com/users', method: 'PATCH', headers: { Authorization: 'Bearer token', 'Content-Type': 'application/json', }, credentials: 'include', }, preflightResponse: { status: 204, headers: { 'Access-Control-Allow-Origin': 'https://app.example.com', 'Access-Control-Allow-Methods': 'GET, POST, PATCH', 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Allow-Credentials': 'true', }, }, }); console.log(result.allowed); // false console.log(result.stage); // "preflight" console.log(result.issues.map((issue) => issue.code)); // [ // "PREFLIGHT_METHOD_NOT_SAFELISTED", // "PREFLIGHT_HEADERS_NOT_SAFELISTED", // "HEADER_NOT_ALLOWED" // ] ``` 该请求被拒绝,因为 `Authorization` 触发了预检(preflight)请求,但未被包含在 `Access-Control-Allow-Headers` 中。每个问题都包含一个稳定的 `code`(代码)、一个 `severity`(严重程度)、一条易于理解的 `message`(消息),通常还附带一个实用的 `suggestion`(建议)。 ## 简单请求 不需要预检(preflight)的跨域请求需要提供实际的资源响应: ``` const result = analyzeCors({ request: { origin: 'https://app.example.com', url: 'https://api.example.com/health', method: 'GET', }, response: { status: 200, headers: { 'Access-Control-Allow-Origin': '*', }, }, }); result.allowed; // true result.preflightRequired; // false ``` ## 预检与实际响应 对于需要预检的请求,请传入 `preflightResponse` 以确定浏览器是否允许发送实际请求。如果同时拥有资源响应,请将其作为 `response` 传入以完成分析: ``` const result = analyzeCors({ request: { origin: 'https://app.example.com', url: 'https://api.example.com/users/42', method: 'DELETE', }, preflightResponse: { status: 204, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'DELETE', }, }, response: { status: 204, headers: { 'Access-Control-Allow-Origin': '*', }, }, }); result.allowed; // true result.stage; // "response" ``` ## 结果阶段 | `stage` | 含义 | | ------------- | ------------------------------------------------------------------------------------ | | `same-origin` | CORS 规则不适用,且不会检查响应头。 | | `preflight` | OPTIONS 响应失败,或者虽然通过了预检但未提供实际响应。 | | `response` | 已对实际的跨域响应进行评估,或者该响应为必填项但未被提供。 | 当仅有预检结果的 `allowed` 为 `true` 时,浏览器可以发送实际请求。但这并不意味着最终的资源响应一定能通过其自身的 CORS 检查。 ## Public API ### `analyzeCors(input)` 返回一个不可变的 `CorsAnalysisResult`: ``` interface CorsAnalysisResult { readonly allowed: boolean; readonly crossOrigin: boolean; readonly preflightRequired: boolean; readonly stage: 'same-origin' | 'preflight' | 'response'; readonly issues: readonly CorsIssue[]; } ``` 该包还导出了所有公共输入和结果的 TypeScript 类型,包括 `AnalyzeCorsInput`、`CorsRequest`、`CorsResponse`、`CorsIssue` 和 `CorsIssueCode`。 普通的 CORS 失败永远不会抛出异常。`analyzeCors` 仅在 API 输入无效时抛出 `TypeError`, 例如格式错误的 URL、HTTP 方法或响应头、不支持的凭证值,或无效的响应状态码。 `request.url` 必须是不包含内嵌凭证的绝对 HTTP(S) URL。 `request.origin` 只能包含 HTTP(S) 协议、主机和可选端口;同时也接受序列化后的不透明 origin `"null"`。 ## 支持的规则 初始版本模拟了当前 [Fetch Standard CORS algorithm](https://fetch.spec.whatwg.org/#cors-protocol)(Fetch 标准 CORS 算法)的相关部分,包括: - 带有默认端口标准化的同源比较; - 兼容 Fetch 的方法标准化和 CORS 安全名单方法; - 安全名单内的请求头名称、值、内容类型、长度以及单字节范围; - 会触发预检(preflight)的方法和请求头名称; - 预检 2xx 状态码检查以及 allow-method/allow-header 检查; - 精确匹配和通配符允许的 origin; - 凭证请求限制以及区分大小写的 `true` 凭证值; - 不区分大小写的 HTTP 请求头名称与区分大小写的 HTTP 方法标识符; - `Access-Control-Allow-Headers: *` 永远不涵盖 `Authorization` 的特殊规则。 `request.headers` 必须只包含由作者控制的请求头,例如传递给 `fetch` 的请求头。 提供由浏览器管理的请求头(如 `Origin` 或 `Cookie`)属于无效输入,并会抛出 `TypeError`。 ## 局限性 此版本有意未对重定向、浏览器预检缓存(preflight cache)、私有网络访问(Private Network Access)、响应头暴露、cookies 或常规 HTTP/网络层面的成功状态进行建模。例如,即使是一个 `404` 响应,只要其 CORS 响应头有效,它依然可能是 CORS 允许通过的。 ## 开发说明 开发环境要求 Node.js 20.19 或更高版本。 ``` npm ci npm run check ``` 可用脚本: - `npm run build` 将 ESM、CommonJS、source map 和类型声明构建输出到 `dist/` 目录。 - `npm test` 运行一次 Vitest 测试套件。 - `npm run test:watch` 以监视模式(watch mode)运行 Vitest。 - `npm run lint` 使用 ESLint 检查项目。 - `npm run format` 使用 Prettier 格式化项目。 - `npm run format:check` 验证格式而不修改文件。 - `npm run typecheck` 在 TypeScript 严格模式下检查项目,不输出文件。 - `npm run check` 运行格式化检查、lint 检查、类型检查、测试以及生产环境构建。 在发布期间,`prepublishOnly` 会运行非构建类的检查,而 `prepack` 会创建一个全新的生产构建。将构建步骤放在 `prepack` 中可以避免在正常的 npm publish 生命周期中重复运行,同时依然确保打包文件(tarball)包含最新的输出。 在发布之前,请使用 `npm pack --dry-run` 检查包的白名单。 ## 社区 在参与之前,请参阅[贡献指南](https://github.com/MMT40/of-cors/blob/main/CONTRIBUTING.md)、 [安全政策](https://github.com/MMT40/of-cors/blob/main/SECURITY.md)和 [行为准则](https://github.com/MMT40/of-cors/blob/main/CODE_OF_CONDUCT.md)。 ## 许可证 MIT标签:CORS, MITM代理, Syscall, TypeScript, Web开发, 安全插件, 自动化攻击, 诊断工具