nark-sh/nark

GitHub: nark-sh/nark

nark 是一个 TypeScript 静态扫描器,通过内置的 npm 包 profile 库检测代码中缺失的运行时错误处理,帮助团队在发布前预防生产环境崩溃。

Stars: 26 | Forks: 0

nark

# nark **针对 npm 包的 Profile 覆盖扫描器 — 在生产之前发现缺失的错误处理。** nark 会根据一个包含 169+ 个包 profile 的精选库,扫描你的 TypeScript 代码库,找出缺失错误处理的地方。可以把它看作是一个 linter,但专门针对运行时故障模式 — 例如未处理的 promise 拒绝、缺失的 `.on('error')` 监听器、未捕获的 API 异常。

Example nark output: three violations against axios, stripe, and @prisma/client

[![npm 版本](https://img.shields.io/npm/v/nark.svg)](https://www.npmjs.com/package/nark) [![Socket 徽章](https://socket.dev/api/badge/npm/package/nark)](https://socket.dev/npm/package/nark) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/nark-sh/nark/badge)](https://scorecard.dev/viewer/?uri=github.com/nark-sh/nark) [![状态](https://img.shields.io/badge/status-page-8364EF)](https://nark.sh/status) [![许可证: AGPL v3](https://img.shields.io/badge/license-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) ## 自行验证 我们试图用“来,验证吧”来代替“相信我”。以下每一项声明都指向你可以在自己的机器上重新检查,或者在我们无法控制的第三方页面上查看的内容: - **源码公开。** 仓库位于 https://github.com/nark-sh/nark — AGPL-3.0 协议,没有闭源二进制文件。 - **来源证明。** 通过 [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) 使用 `npm publish --provenance` 发布。[npm 页面](https://www.npmjs.com/package/nark) 上的绿色 Provenance 徽章以加密方式将发布的 tarball 与确切的 git commit 和 CI 运行关联起来。使用 `npm audit signatures` 验证任何已安装的副本。 - **第三方供应链评分。** 由 [Socket](https://socket.dev/npm/package/nark) 持续评分。我们不提交任何内容 — Socket 会自动扫描每个已发布的版本。 - **第三方项目健康度评分。** [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/nark-sh/nark) 每周针对公开仓库重新运行。任何人都可以从头重新推导该分数。 - **评分历史。** 每日更新的 Socket 评分时间线位于 https://nark.sh/status — 每一条记录都提交到了公开的工作流日志中。 - **tarball 中的内容。** 运行 `npm pack --dry-run nark` — 你只会看到 `bin/`、`dist/`、`schema/`、`demo/`、`FORAIAGENTS.md`。没有其他东西。 - **无安装脚本。** `npm install nark --ignore-scripts` 会产生完全相同的安装。nark 在安装时不会在你的机器上运行任何代码。 - **完整的遥测详情。** Schema、endpoint 和三个 opt-out 路径记录在 https://nark.sh/telemetry。请参阅下方的 [遥测](#telemetry)。 - **负责任的披露。** [SECURITY.md](./SECURITY.md) — 48 小时确认,security@nark.sh。 ## 坦诚对待误报 nark 使用静态分析。静态分析是一种近似方法 — 扫描器在不运行代码的情况下对其进行推理,因此如果你的项目在 nark 目前还无法看到的层面上处理了错误(例如:中心的 Express 或 NestJS 错误中间件、React 错误边界、上一层的 retry wrapper、调用函数中的 try-catch,或者内部会进行捕获的库),那么一些被标记的违规可能会是误报。 我们为了保持误报率诚实所做的事情: - **Profile 基于证据。** 每一个后置条件都会引用包的文档或源代码。我们不会凭空发明规则。 - **扫描器能识别常见的框架模式。** 它理解 TanStack Query 全局处理程序、TanStack Router loader 回调、tRPC callback wrapper、Fastify `setErrorHandler`、Express 和 Koa 错误中间件、NestJS exception filter 以及其他几种架构模式。这个列表每月都在增加。 - **我们在发布前进行衡量。** 每次扫描器升级在发布前都会针对真实的代码库进行验证。 如果你发现了误报:在该行旁边使用 `// nark-ignore: ` 进行抑制,或者访问 [github.com/nark-sh/nark/issues](https://github.com/nark-sh/nark/issues) 提交一个包含文件:行号 的 issue — 我们会根据真实的报告更新 Profile 或扫描器。我们并不声称扫描器是完美的。我们声称的是,它对自己知道什么和不知道什么是诚实的。 ## 快速开始 ### 60 秒内体验(无需配置) ``` npx nark --demo ``` 这会针对一个内置的示例项目运行 nark,该项目包含有意的 `axios`、`stripe` 和 `@prisma/client` 违规。输出结果与你扫描自己代码的真实情况完全一致 — 只是它保证报告不会为空。 使用 `--verbose` 运行可以查看每个违规的详细信息(代码片段、来源和修复指导)以及仓库健康度指标:

Preview: verbose Verification Report with per-violation code snippets, sources, and fix guidance

以可搜索文本形式显示详细的验证报告(包含代码片段的每个违规的详细信息) ``` Nark Verification Report ──────────────────────────────────────────────────────────────────────────────── Summary: Files analyzed: 3 Contracts applied: 3 Timestamp: 2026-06-23T23:18:02.014Z Git commit: c170802b (dirty) Git branch: main Package Discovery & Coverage ──────────────────────────────────────────────────────────────────────────────── Total packages: 3 Packages with contracts: 3 (100.0%) Packages without contracts: 0 ✓ Packages with contracts: @prisma/client@* (contract v1.1.0) axios@* (contract v1.0.0) stripe@* (contract v1.0.0) Violations by Package ──────────────────────────────────────────────────────────────────────────────── @prisma/client (2 violations) Errors: 2 | Warnings: 0 | Info: 0 ✗ nark-dev/nark/demo/src/users.ts:18:22 No try-catch block found. PrismaClientKnownRequestError with code 'P2002' - this will crash the application. Package: @prisma/client.create() Contract: unique-constraint-violation 14 | // throws Prisma's P2002 unique-constraint error. The caller sees a generic 15 | // rejection and a 500 — the user sees "something went wrong" instead of 16 | // "this email is already registered." 17 | export async function createUser(email: string, name: string) { > 18 | const user = await prisma.user.create({ data: { email, name } }); 19 | return user; 20 | } 21 | 22 | // Looks up a user by email. Also fix in same handler: ↳ Also missing: PrismaClientKnownRequestError with code 'P2003' ↳ Also missing: PrismaClientValidationError ↳ Also missing: PrismaClientInitializationError or PrismaClientRustPanicError Fix: Caller MUST catch P2002 errors and handle duplicate key violations gracefully. Extract conflicting field from error.meta.target. DO NOT retry without changing the unique field value. Docs: https://www.prisma.io/docs/reference/api-reference/error-reference#p2002 ✗ nark-dev/nark/demo/src/users.ts:28:22 No error handling found. null — required handling missing. Package: @prisma/client.findUnique() Contract: record-not-found 24 | // reject on connection-pool exhaustion, statement-timeout, or a transient 25 | // network blip to the database. Without handling, a slow query during 26 | // peak traffic crashes the request instead of returning a graceful 503. 27 | export async function getUserByEmail(email: string) { > 28 | const user = await prisma.user.findUnique({ where: { email } }); 29 | return user; 30 | } 31 | Also fix in same handler: ↳ Also missing: PrismaClientInitializationError Fix: Caller MUST check if result is null before accessing properties. Code that assumes findUnique always returns a record will crash. Docs: https://www.prisma.io/docs/concepts/components/prisma-client/crud#findunique axios (3 violations) Errors: 2 | Warnings: 1 | Info: 0 ✗ nark-dev/nark/demo/src/api-client.ts:17:26 No try-catch block found. AxiosError with error.response containing the error response - this will crash the application. Package: axios.get() Contract: error-4xx-5xx 13 | // VIOLATION (axios.error-4xx-5xx): no try/catch — a 404 or a network blip 14 | // rejects the promise, the caller sees an unhandled rejection, and depending 15 | // on the Node version the process may exit. 16 | export async function fetchUserProfile(userId: string) { > 17 | const response = await axios.get(`${API_BASE}/users/${userId}`); 18 | return response.data; 19 | } 20 | 21 | // Submits a comment to the upstream service. Also fix in same handler: ↳ Also missing: AxiosError with error.response.status === 429 ↳ Also missing: AxiosError with error.request populated but error.response === undefined ↳ Also missing: AxiosError with both error.request === undefined and error.response === undefined Fix: Caller MUST catch AxiosError and check error.response.status to distinguish between client errors (4xx) and server errors (5xx). Docs: https://axios-http.com/docs/handling_errors ✗ nark-dev/nark/demo/src/api-client.ts:26:26 No try-catch block found. AxiosError with error.response - this will crash the application. Package: axios.post() Contract: error-4xx-5xx 22 | // VIOLATION (axios.error-4xx-5xx): same shape as above, but on a POST — 23 | // even more dangerous because retrying naively could double-post the 24 | // comment without an idempotency key. 25 | export async function postComment(userId: string, text: string) { > 26 | const response = await axios.post(`${API_BASE}/comments`, { userId, text }); 27 | return response.data; 28 | } 29 | 30 | // Fetches search results. Also fix in same handler: ↳ Also missing: AxiosError with error.response.status === 429 ↳ Also missing: AxiosError with error.response === undefined Fix: Caller MUST catch AxiosError and inspect error.response.status Docs: https://axios-http.com/docs/handling_errors ⚠ nark-dev/nark/demo/src/api-client.ts:36:28 Rate limit response (429) is not explicitly handled. Consider implementing retry logic with exponential backoff. Package: axios.get() Contract: rate-limited-429 32 | // only logs and re-throws. A 429 from a rate-limited API should trigger a 33 | // backoff/retry; here it just bubbles up as a generic "request failed." 34 | export async function searchPosts(query: string) { 35 | try { > 36 | const response = await axios.get(`${API_BASE}/search`, { 37 | params: { q: query }, 38 | }); 39 | return response.data; 40 | } catch (error) { Fix: Caller MUST either: 1. Implement exponential backoff retry logic with the Retry-After header, OR 2. Explicitly handle 429 as a terminal error and surface to the user, OR 3. Use a request queue that respects rate limits. Silently catching and ignoring 429 without retry logic is a violation. Docs: https://axios-http.com/docs/handling_errors stripe (2 violations) Errors: 2 | Warnings: 0 | Info: 0 ✗ nark-dev/nark/demo/src/payments.ts:23:24 No try-catch block found. StripeCardError with error.type === 'card_error' - this will crash the application. Package: stripe.create() Contract: card-error 19 | amount: number, 20 | currency: string, 21 | source: string, 22 | ) { > 23 | const charge = await stripe.charges.create({ amount, currency, source }); 24 | return charge.id; 25 | } 26 | 27 | // Creates a new Stripe customer record at signup time. Also fix in same handler: ↳ Also missing: StripeRateLimitError with error.type === 'rate_limit_error' ↳ Also missing: StripeAuthenticationError with error.type === 'authentication_error' ↳ Also missing: Error with no error.type (connection error) Fix: Caller MUST catch StripeCardError and handle gracefully. Display user-friendly message based on error.decline_code. DO NOT retry card_error without user intervention. Docs: https://stripe.com/docs/error-handling ✗ nark-dev/nark/demo/src/payments.ts:32:26 No try-catch block found. StripeCardError with error.type === 'card_error' - this will crash the application. Package: stripe.create() Contract: card-error 28 | // VIOLATION (stripe.error-4xx-5xx): no try/catch on the customer.create. 29 | // A duplicate-email collision or a transient 502 from Stripe will throw 30 | // and the signup flow will appear to silently fail. 31 | export async function registerCustomer(email: string) { > 32 | const customer = await stripe.customers.create({ email }); 33 | return customer.id; 34 | } 35 | Also fix in same handler: ↳ Also missing: StripeRateLimitError with error.type === 'rate_limit_error' ↳ Also missing: StripeAuthenticationError with error.type === 'authentication_error' ↳ Also missing: Error with no error.type (connection error) Fix: Caller MUST catch StripeCardError and handle gracefully. Display user-friendly message based on error.decline_code. DO NOT retry card_error without user intervention. Docs: https://stripe.com/docs/error-handling ──────────────────────────────────────────────────────────────────────────────── Overall Summary: Total violations: 7 Errors: 6 Warnings: 1 Info: 0 ✗ FAILED ╔═══════════════════════════════════════════════════════════════════════════════╗ ```

Preview: verbose Analysis Report with coverage summary, repository health metrics, violations by package, and recommendations

以可搜索文本形式显示详细的分析报告(覆盖率摘要、健康度指标、建议) ``` ║ Nark Analysis Report ║ ╚═══════════════════════════════════════════════════════════════════════════════╝ Repository: demo Analyzed: 6/23/2026, 7:18:02 PM Git Commit: c170802b Git Branch: main ✅ CODE HEALTH SCORE: 0/100 📊 COVERAGE SUMMARY ──────────────────────────────────────────────────────────────────────────────── • Files Analyzed: 3 • Call Sites Evaluated: 7 • Contracts Applied: 3 • Violations Found: 7 ! • Checks Passed: 0 ✓ 📈 REPOSITORY HEALTH METRICS ──────────────────────────────────────────────────────────────────────────────── • Error Handling Compliance: 0% • Package Coverage: 100% • Code Maturity: LOW • Risk Level: HIGH ✗ VIOLATIONS BY PACKAGE ──────────────────────────────────────────────────────────────────────────────── ✗ axios 3 violations in 3 call sites (2 errors, 1 warnings) ✗ @prisma/client 2 violations in 2 call sites (2 errors) ✗ stripe 2 violations in 2 call sites (2 errors) 3 packages with contracts checked 0 fully compliant ✓ 3 with violations ✗ 🎯 RECOMMENDATIONS ──────────────────────────────────────────────────────────────────────────────── 1. Fix 7 remaining violations to achieve 100% compliance 2. Run scan after fixes to verify improvements 3. Add to CI to prevent future violations ════════════════════════════════════════════════════════════════════════════════ Report generated by Nark v3.2.0 Next scan: Enable CI integration for continuous monitoring ════════════════════════════════════════════════════════════════════════════════ ```
### 或者从源码构建 ``` # 并排 Clone nark 和 contract corpus git clone https://github.com/nark-sh/nark.git git clone https://github.com/nark-sh/nark-corpus.git # 构建 cd nark npm install npm run build # 通过扫描 nark 本身来验证其是否有效 node bin/nark.js --tsconfig ./tsconfig.json --corpus ../nark-corpus # 然后扫描你自己的项目 node bin/nark.js --tsconfig /path/to/your/project/tsconfig.json --corpus ../nark-corpus ``` 扫描 nark 自己的仓库是一个很好的冒烟测试 — 你应该会看到一份干净的报告,包含 0 个违规以及检测到少量已签约的包。 如果你的项目没有 `tsconfig.json`,nark 会自动生成一个最小化的配置。 如果 `nark-corpus` 作为 npm 包安装,nark 会自动找到它 — 不需要 `--corpus` 标志。 ## 添加到你的项目中 将 nark 安装为 dev dependency,让你的整个团队都能运行它: ``` npm install --save-dev nark # 或者 pnpm add -D nark ``` 在你的 `package.json` 中添加一个脚本: ``` { "scripts": { "nark": "nark" } } ``` 然后使用以下命令扫描: ``` npm run nark # 或者直接 pnpm nark ``` 对于 CI,遇到违规时让构建失败: ``` pnpm nark --fail-threshold warning ``` ## 它能发现什么 nark 知道 169+ 个 npm 包在运行时是如何失败的。对于每一个包,它会检查你的代码是否处理了这些故障。违规示例: ``` ERROR axios.get() call without try-catch → src/api/client.ts:42 Contract: axios → postcondition "network-error-handling" Fix: Wrap in try-catch and handle AxiosError WARN redis.connect() without .on('error') listener → src/cache/redis.ts:18 Contract: ioredis → postcondition "connection-error-listener" Fix: Register error listener before calling connect() ``` ## 工作原理 1. **契约** 定义了包是如何失败的(它们抛出什么错误,发出什么事件) 2. **扫描器** 遍历你的 TypeScript AST 并查找已签约包的调用 3. **分析器** 检查每个调用点是否有适当的错误处理 4. **报告器** 输出包含严重程度、位置和修复建议的违规 契约是 [nark-corpus](https://github.com/nark-sh/nark-corpus) 包中的 YAML 文件。扫描器使用 TypeScript 的 compiler API — 没有运行时执行。 ## 输出 结果会输出到你项目目录下的 `.nark/` 中: ``` .nark/ ├── config.yaml # Scanner configuration ├── latest -> scans/002/ # Symlink to most recent scan ├── scans/ │ ├── 001/ │ │ ├── summary.json # Machine-readable results │ │ └── summary.md # Human-readable report │ └── 002/ │ ├── summary.json │ └── summary.md └── violations/ ├── axios/ │ └── network-error-handling.md └── ioredis/ └── connection-error-listener.md ``` ## 命令 ### 扫描(默认) ``` # 扫描当前目录 nark # 扫描特定项目 nark --tsconfig ./tsconfig.json # 包含测试文件 nark --include-tests # 遇到警告时使 CI 失败 nark --fail-on-warnings # 仅报告模式(总是 exit 0 —— 永不阻断 CI) nark --report-only # 如果发现任何警告或错误则 exit 1 nark --fail-threshold warning # 用于 GitHub Code Scanning 的 SARIF 输出 nark --sarif nark --sarif-output results.sarif # 紧凑摘要输出(默认为完整报告) nark --quiet ``` ### 显示 检查 nark 的配置和受支持的包: ``` # 打印 nark 版本、Node 版本、corpus 路径、contract 数量 nark show version # 列出所有 contracted 包(易于人类阅读) nark show supported-packages # 以 JSON 格式列出 nark show supported-packages --json # 显示当前的 auth/API endpoint 配置 nark show deployment ``` ### 遥测 nark 收集匿名的使用数据,以帮助确定开发的优先级。遥测**默认开启**,并且可以随时禁用。首次运行时会显示通知。
可选:app.nark.sh 上的托管仪表板

如果开启了遥测并且你已经运行过 nark login,你的扫描结果就会显示在 app.nark.sh 的托管仪表板中,其中包含趋势 线、每个包的汇总以及 PR 级别的差异视图。该仪表板 完全是可选的 — 无论哪种情况,nark 都会在本地运行相同的扫描,并且 所有结果始终会写入到你项目的 .nark/ 中。该 仪表板只是锦上添花,而不是强制依赖。

**收集的内容:** nark 版本、OS/arch、Node.js 版本、检测到的 npm 包名(仅限公开的包名)、匹配的 contract ID、每个 contract 的违规计数、扫描持续时间、CI 模式标志,以及你的 git 远程 URL 的可选 SHA256 哈希值。 **绝不收集的内容:** 源代码、文件路径、变量名、目录名、仓库名、git 历史记录、用户身份、机器主机名或 IP 地址(在服务器端已被剥离)。 ``` # 检查 telemetry 状态 nark telemetry status # 永久退出 nark telemetry off # 通过环境变量退出(非常适合 CI) export NARK_TELEMETRY=off # 遵循标准的 DO_NOT_TRACK 约定 export DO_NOT_TRACK=1 # 重新加入 nark telemetry on ``` 你也可以在 `.nark/config.yaml` 中设置 `telemetry: false`,或者使用 `NARK_API_URL` 将遥测指向不同的 endpoint。 如果遥测发送超时(网络缓慢、开发服务器冷启动),可以针对每次扫描增加超时时间:`nark --telemetry-timeout=10000`(毫秒;默认 5000)。无论遥测是否成功,扫描结果都会保存在本地。 了解更多:[https://nark.sh/telemetry](https://nark.sh/telemetry) ### 崩溃报告 当 nark 遇到意外错误时,会将匿名崩溃报告发送到 Sentry。这有助于捕获影响真实用户的 bug。不包含任何源代码、文件路径或身份识别信息(路径已通过 `beforeSend` 清除)。仅对 25% 的错误进行采样,以最大限度地减少开销。 崩溃报告遵循与遥测相同的 opt-out 标志,此外还有一个专用的 kill switch: ``` # 仅禁用崩溃报告(scan telemetry 仍然会传输) export NARK_SENTRY=off # 禁用所有功能(telemetry + 崩溃报告) export NARK_TELEMETRY=off # 遵循标准的 DO_NOT_TRACK 约定 export DO_NOT_TRACK=1 ``` ### 身份验证 nark 支持从同一台机器登录到**多个 workspace**(组织)。凭证存储在 `~/.nark/credentials.json` 中,以组织 slug 为键,文件权限为 `0600`。 ``` # 登录(基于浏览器的 device flow;从下拉菜单中选择 org) nark login # 在浏览器中预选一个 organization nark login --org acme # 显示活跃的 workspace 及其解析方式 nark whoami # 列出所有已登录的 workspace(标记 default + 最近使用) nark workspace # 切换全局 default workspace nark workspace use acme # 将此 repo 绑定到 workspace(写入 .nark/config.json —— 请提交此文件!) nark workspace use --here acme # 在本地重命名 workspace alias nark workspace rename old-slug new-slug # 登出 default workspace(自动提升至下一个最近使用的) nark logout # 登出特定的 workspace nark logout --org acme # 登出所有 workspace(删除 ~/.nark/credentials.json) nark logout --all ``` #### 多 workspace 流程 如果你属于多个组织(例如个人组织 + 团队组织),请分别登录: ``` nark login --org personal nark login --org acme ``` 第一次登录会成为你的默认设置。后续登录**不会**更改默认设置 — 请使用 `nark workspace use ` 进行切换。 #### 绑定特定仓库的 workspace(`.nark/config.json`) 要将仓库固定到特定的 workspace,请提交一个 `.nark/config.json`: ``` { "workspace": "acme" } ``` 这与 Vercel 用于 `.vercel/project.json` 的模式相同。该文件**应该被提交** — 它让每个贡献者的 workspace 都变得明确,并避免了“这个扫描结果落入了谁的组织?”的混乱。 作为兜底,也会兼容带有 `workspace` 字段的旧版 `.narkrc.json`。如果两个文件同时存在,`.nark/config.json` 优先,并会给出一次性的警告,建议删除旧文件。 #### 解析优先级 当 nark 需要 token(用于遥测、仪表板上传等)时,它会按顺序遍历此链条 — 第一个匹配的获胜: 1. `NARK_API_KEY` 环境变量 2. `NARK_TOKEN` 环境变量(已弃用;会发出一次性警告) 3. `--org ` / `-w ` 命令行标志 4. `.nark/config.json` 中的 `workspace` 字段(CWD 或其父目录) 5. `.narkrc.json` 中的 `workspace` 字段(CWD 或其父目录,旧版) 6. `~/.nark/credentials.json` 中的 `default` 字段 7. 存储中恰好有一个 workspace(静默设为默认) 8. 错误 — 调用者会要求你设置以上之一 #### 环境变量 | 变量 | 状态 | 备注 | | --------------- | ------------------ | ---------------------------------------------------------------------------------------- | | `NARK_API_KEY` | 规范 | 在 CI 中为无人值守的运行设置。 | | `NARK_TOKEN` | 已弃用 | 仍然可用;每个进程会打印一次警告。将在 nark **v2.0** 中被移除。 | | `NARK_API_URL` | 可选的覆盖项 | 默认为 `https://app.nark.sh`。适用于 staging/自托管的 endpoint。 | #### 从早期的 nark 版本迁移 (NARK_TOKEN) 如果你有现存的 `~/.nark/credentials` (v1) 文件,nark 会在你第一次运行 nark **2.1.0** 或更高版本的任何命令时自动迁移它: - 凭证会被复制到新的 `~/.nark/credentials.json` (v2) 中, 以哨兵 slug `default` 为键。 - 单行 stderr 输出 — `Migrated nark credentials to v2 (multi-workspace).` — 会打印一次。会写入一个 `migratedAt` 时间戳,该消息 永远不会再次出现。 - 在写入新文件后,旧版 v1 文件会被删除。 迁移后,运行 `nark login --org ` 将你的团队 workspace 添加到已迁移的 workspace 旁边,然后运行 `nark workspace use ` 选择你的默认 workspace。 ### CI(文件级差异感知扫描) 仅扫描当前 PR/branch 中更改的文件 — 非常适合 GitHub Actions: ``` # 扫描自 HEAD~1 以来更改的文件 nark ci # 扫描自特定 commit 以来更改的文件 nark ci --baseline-commit # 带有 SARIF 输出(用于 GitHub Check Runs / Code Scanning) nark ci --sarif nark ci --sarif-output results.sarif ``` ### 推荐 GitHub Actions 工作流 可直接复制粘贴的起点。在你进行集成**之前**有三件事需要了解 — 这些都是我们在自己的 SaaS 上运行 nark 时通过惨痛教训学到的。 ``` name: Nark on: push: branches: [main] pull_request: branches: [main] jobs: nark: name: Nark scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: { version: 9 } - uses: actions/setup-node@v4 with: { node-version: '20' } - run: pnpm install # Generate any client code Nark's TS analyzer needs to resolve types. # Prisma is the common case; drop this step if your project doesn't use it. - run: pnpm prisma generate - name: Run nark id: nark continue-on-error: true # SHADOW MODE — see "Rollout pattern" below run: | npx -y nark@latest \ --tsconfig path/to/your/tsconfig.json \ --output nark-audit.json env: # Default Node old-gen heap on GitHub-hosted runners is ~2 GB. # Non-trivial TS programs (Next.js + Prisma + a few dozen routes) # OOM under that with exit 134. Runners have 16 GB physical, so # 8 GB is well within budget. Skip this only on a tiny project. NODE_OPTIONS: '--max-old-space-size=8192' NARK_TELEMETRY: 'false' # Without this verify step, an OOM or crash leaves nark-audit.json # missing, upload-artifact emits a warning instead of failing, and # `continue-on-error: true` reports the whole job as green. We hit # this 18 times in a row before we noticed. Fail loud when there's # no audit JSON. - name: Verify nark produced an audit if: steps.nark.outcome == 'success' || steps.nark.outcome == 'failure' run: | test -f nark-audit.json || { echo "::error::Nark did not produce nark-audit.json — scan crashed before writing output. See the Run nark step log." exit 1 } - name: Upload nark audit if: always() uses: actions/upload-artifact@v4 with: name: nark-audit path: nark-audit.json retention-days: 30 ``` **需要知道的三件事:** 1. **`NODE_OPTIONS: '--max-old-space-size=8192'`** — 没有这个,中等规模的 TypeScript 项目会内存溢出 (OOM),报错 `FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory`。GitHub 托管的运行器将 Node 的老生代堆内存默认限制在约 2 GB;请将其调高。 2. **“验证 nark 是否生成了审计”步骤** — `continue-on-error: true` + `actions/upload-artifact@v4` 是一个脆弱的组合。当扫描器崩溃时,审计 JSON 不会被写入,上传步骤只会发出警告而不是失败,因此作业仪表板依然显示绿色。验证步骤使得“静默成功”变得不可能。 3. **上线模式** — 从 `continue-on-error: true` 开始。在没有任何 nark 感知的代码库上的首次运行通常会发现数十个问题。对它们进行分级处理(将误报添加到 `.nark/suppressions.json`,修复真正的违规),然后去掉 `continue-on-error` 以阻止合并。分为三个阶段: - 阶段 1(第 1 周):扫描器运行,捕获基线,合并不受阻碍。 - 阶段 2(第 1-2 周):审查基线,附带原因进行抑制,修复真正的问题。 - 阶段 3:移除 `continue-on-error: true`。新的违规将阻止 PR。 ### `--diff`(行级过滤) 将违规过滤为仅针对 git diff 范围内实际触及的行。未修改文件中未触及行上的既有违规会被排除在外 — 这符合 CodeRabbit / Greptile 风格 PR 审查机器人的姿态。 ``` # 仅限你的 branch 相比 main 引入的违规 nark --diff main..HEAD --tsconfig tsconfig.json # 针对特定的 base SHA(在 PR CI 中很有用) nark --diff $BASE_SHA..HEAD --tsconfig tsconfig.json # 针对 GitHub Actions 中的 PR base nark --diff origin/${{ github.base_ref }}..HEAD --tsconfig tsconfig.json ``` 过滤后的计数驱动退出代码 — `nark --diff` 仅在 diff 引入的违规满足 `--fail-threshold` 时以非零状态退出。既有违规不会导致构建失败。 与 `--changed-files`(文件级)共存。当两者同时传递时,`--diff` 优先,因为在相同的变更集上,行级限制始终至少与文件级限制一样严格。 重命名的文件会被妥善处理 — 重命名文件上的违规仅根据新路径进行匹配。 ### 分级审查 审查并对违规进行分类: ``` # 列出未分诊的违规 nark triage list # 标记一个违规 nark triage mark true-positive nark triage mark false-positive --reason "handled by framework" nark triage mark wont-fix --reason "acceptable risk" # 显示 triage 统计信息 nark triage summary ``` 误报会在未来的扫描中被自动抑制。 ### 抑制 ``` # 列出所有 suppressions nark suppressions list # 添加一个 suppression nark suppressions add --fingerprint --reason "handled by middleware" # 查找并移除过期的 suppressions nark suppressions clean ``` ### 其他 ``` # 在项目中初始化 .nark/ 配置 nark init # 压缩 scan 历史(保留 triage 决策) nark compact # 获取 AI agent 指令(适用于 Claude、Cursor 等) nark --instructions-path ``` ## 配置文件 在你的项目根目录下放置一个 `.nark/config.yaml` 以持久化 CLI 选项。CLI 标志始终会覆盖文件值。 ``` # .nark/config.yaml # Fail 阈值:error | warning | info(默认值:error) failThreshold: error # 总是 exit 0(仅报告模式) # reportOnly: false # 输出路径 output: json: .nark/latest.json sarif: .nark/results.sarif # Exclude 模式 exclude: - '**/*.test.ts' - '**/node_modules/**' # 包含 draft/开发中的 contracts # includeDrafts: false ``` | 字段 | 描述 | 默认值 | |-------|-------------|---------| | `tsconfig` | tsconfig.json 的路径 | `./tsconfig.json` | | `corpus` | corpus 目录的路径 | 自动检测 | | `failThreshold` | 触发退出代码 `1` 的严重级别 (`error`\|`warning`\|`info`) | `error` | | `reportOnly` | 无论是否有违规都始终退出 `0` | `false` | | `output.json` | JSON 审计记录的自定义路径 | 自动 | | `output.sarif` | SARIF 输出的自定义路径 | 无 | | `include` | 用于限制分析文件范围的 Glob 匹配模式 | 所有 `.ts` 文件 | | `exclude` | 用于排除文件的 Glob 匹配模式 | 无 | | `includeDrafts` | 包含草案阶段的 contract | `false` | | `includeTests` | 包含测试文件 | `false` | | `includeDeprecated` | 包含已弃用的 contract | `false` | | `telemetry` | 开启遥测 | `true` | ## AI Agent 集成 nark 包含 `FORAIAGENTS.md` — 这是一个机器可读的指令文件,用于教导 AI agent 如何解释和修复违规。将你的 agent 指向它: ``` # 获取指令文件的路径 nark --instructions-path # 或者在你的 AI 配置中引用 # Claude Code:添加到 CLAUDE.md # Cursor:添加到 .cursorrules ``` ## 退出代码 | 代码 | 含义 | |------|---------| | `0` | 干净的扫描 — 在失败阈值及以上没有违规 | | `1` | 在 `--fail-threshold` 及以上发现违规(默认:`error`) | | `2` | 内部错误(糟糕的 tsconfig、缺失 corpus 等) | 使用 `--report-only` 可始终获得 `0`,或者使用 `--fail-threshold warning` 也可以在出现 warning 时阻止运行。 ## CLI 选项 | 标志 | 描述 | 默认值 | |------|-------------|---------| | `--tsconfig ` | tsconfig.json 的路径 | `./tsconfig.json` | | `--corpus ` | corpus 目录的路径 | 内置 | | `--output ` | 审计 JSON 的输出路径 | 自动生成 | | `--project ` | 用于发现 package.json 的项目根目录 | cwd | | `--no-terminal` | 禁用终端输出(仅输出 JSON) | false | | `--report-only` | 始终退出 0(永不阻止 CI) | false | | `--fail-threshold ` | 如果在该严重级别及以上的违规则退出 1 | `error` | | `--fail-on-warnings` | `--fail-threshold warning` 的简写 | false | | `--sarif` | 将 SARIF 2.1.0 写入 `.nark/results.sarif` | false | | `--sarif-output ` | 将 SARIF 写入自定义路径 | — | | `--diff ` | 行级过滤 — 仅报告 diff 涉及的行上的违规(例如 `main..HEAD`) | — | | `--changed-files ` | 文件级过滤 — 仅报告指定文件中的违规 | — | | `--quiet, -q` | 显示简要摘要而不是完整报告 | false | | `--verbose` | 显示遥测详情、耗时分析和完整的报告路径。 | false | | `--include-tests` | 包含测试文件 | false | | `--include-drafts` | 包含草案阶段的 contract | false | | `--show-suppressions` | 显示已抑制的违规 | false | | `--check-dead-suppressions` | 报告过时的抑制项 | false | | `--fail-on-dead-suppressions` | 发现过时的抑制项时以非零状态退出 | false | ## 从源码构建 ``` # Clone 两个 repos(corpus 是扫描所必需的,而不是构建所必需的) git clone https://github.com/nark-sh/nark.git git clone https://github.com/nark-sh/nark-corpus.git cd nark npm install npm run build npm test ``` 需要 Node.js >= 18。 ## Corpus 契约库 ([nark-corpus](https://github.com/nark-sh/nark-corpus)) 包含 169+ 个契约,涵盖以下包: - **HTTP:** axios, got, node-fetch, undici, superagent - **数据库:** prisma, knex, sequelize, typeorm, drizzle, pg, mysql2, better-sqlite3 - **云服务:** aws-sdk, @google-cloud/*, @azure/* - **身份验证:** jsonwebtoken, bcrypt, passport, @clerk/*, @auth0/* - **消息队列:** bullmq, amqplib, kafkajs - **AI:** openai, @anthropic-ai/sdk, @langchain/* - **以及另外 100 多个...** ## 故障排除 ### `npx nark` 时的 npm 警告 运行 `npx nark` 可能会打印如下警告: ``` npm warn Unknown env config "developer" npm warn Unknown project config "public-hoist-pattern". This will stop working in the next major version of npm. ``` 这些警告来自于你的 `~/.npmrc` 中包含了 pnpm 专用的配置键,而 npm 无法识别它们。它们不是 nark 的错误 — npm 只是在警告这些键对它不起作用。扫描本身运行良好。 **修复方法(任选其一):** - 将 pnpm 专用的键移动到 `~/.pnpmrc` 中(只有 pnpm 会读取该文件)。这是最清晰的分离方式。 - 在 `~/.npmrc` 中为未知的键添加前缀 `_`(例如 `_public-hoist-pattern=...`)。npm 会静默忽略以 `_` 开头的键。 **不要**使用 `npm --silent` 或 `npx --silent` 来抑制它们 — 该标志也会隐藏你希望看到的真实 npm 错误。 ## 许可证 AGPL-3.0 — 详情请参阅 [LICENSE](./LICENSE)。免费用于本地使用、CI/CD 和自托管。SaaS 提供商必须开源修改内容或获取商业许可证。
标签:MITM代理, SOC Prime, TypeScript, 代码审查, 分布式计算, 安全专业人员, 安全插件, 开发工具, 异常处理, 暗色界面, 自动化攻击, 错误基检测, 静态代码分析