canship 0.2.0 → 0.3.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.
@@ -0,0 +1,199 @@
1
+ # canship
2
+
3
+ 面向 JavaScript / TypeScript 项目的本地静态扫描器,检测凭据暴露与访问控制配置错误。扫描不执行项目代码、不上传文件、不联网。
4
+
5
+ 本文档对应 0.3.x,适用于匹配的 [npm 版本](https://www.npmjs.com/package/canship) 或本地构建。
6
+
7
+ ```powershell
8
+ npx canship .
9
+ ```
10
+
11
+ 要求 Node.js ≥18,无运行时依赖。`npx` 可能联网下载软件包;扫描仅使用本地文件与 Git 历史。Git 仓库中无法调用 Git 时,扫描标记为未完成。
12
+
13
+ [English](./README.md)
14
+
15
+ ## 检测范围
16
+
17
+ | 检查项 | 级别 |
18
+ |---|---|
19
+ | 硬编码凭据、私钥及含密码的数据库连接串 | P0 |
20
+ | 公开环境变量中的私密值 | P0 |
21
+ | Supabase 管理员密钥暴露至客户端 | P0 |
22
+ | Git 跟踪及历史 `.env` 文件中的凭据或疑似私密值 | P0 |
23
+ | Supabase 迁移记录中未启用行级安全(RLS)的表 | P1 |
24
+ | Firebase 无条件访问及固定日期测试规则 | P1 |
25
+ | Next.js API 数据操作未识别到鉴权 | P0 / P1 |
26
+ | 携带凭据的 CORS 来源回显或通配符配置 | P1 / P2 |
27
+
28
+ 支持 OpenAI、Anthropic、AWS、Stripe、GitHub、npm、Slack、SendGrid 等凭据格式及常见前端公开环境变量前缀。API 鉴权检查仅覆盖 Next.js `/api`,支持 App Router、Pages Router、路由组和工作区应用。
29
+
30
+ 置信度分为确定(`certain`)和疑似(`likely`),仅描述静态证据,不验证凭据有效性或线上状态。默认只展示确定结果;隐藏的疑似结果仍影响退出码。
31
+
32
+ ## 用法
33
+
34
+ 省略路径时扫描当前目录。
35
+
36
+ | 参数 | 说明 |
37
+ |---|---|
38
+ | `-a`, `--all` | 展示疑似结果 |
39
+ | `--json` | 输出 JSON |
40
+ | `--fix-prompt` | 输出修复指令及独立的人工操作清单 |
41
+ | `--report[=file]` | 写入 HTML,默认 `canship-report.html` |
42
+ | `--sarif[=file]` | 写入 SARIF 2.1.0,默认 `canship.sarif` |
43
+ | `--best-effort` | 无结果时,允许不完整扫描退出 `0` |
44
+ | `--baseline[=file]` | 应用基线,默认 `canship-baseline.json` |
45
+ | `--baseline-write[=file]` | 写入当前结果为基线后退出,同上默认路径 |
46
+ | `--only=ids` | 仅执行匹配规则,支持逗号分隔及重复参数 |
47
+ | `--skip=ids` | 排除匹配规则,支持逗号分隔及重复参数 |
48
+ | `--no-config` | 忽略项目配置 |
49
+ | `--list-rules` | 列出规则及限制,不扫描;支持 `--json` |
50
+ | `--no-excerpts` | 所有报告省略源码摘录,不改变结果和退出码 |
51
+ | `-h`, `--help` | 显示帮助 |
52
+ | `-v`, `--version` | 显示版本 |
53
+
54
+ `--json` 与 `--fix-prompt` 互斥;HTML、SARIF 可与任一模式组合。报告正文为英文,`--all` 对所有格式生效。
55
+
56
+ ### 退出码
57
+
58
+ | 退出码 | 含义 |
59
+ |---|---|
60
+ | `0` | 无结果,且扫描完整或由 `--best-effort` 接受不完整扫描 |
61
+ | `1` | 存在 `certain` 的 P0/P1 结果 |
62
+ | `2` | 存在其他结果,包括被隐藏的 `likely` |
63
+ | `3` | 参数或工具错误,或未被接受的不完整扫描 |
64
+
65
+ 结果退出码优先于不完整状态;`--best-effort` 不改变 `1` 或 `2`。
66
+
67
+ ### 机器可读输出
68
+
69
+ JSON 使用独立于包版本的 `schemaVersion: 1`,npm 包附带 [结构定义](./schemas/scan-report-v1.schema.json)。调用方应兼容新增字段、拒绝不支持的结构版本;`--list-rules --json` 为独立的 `kind: "rule-catalog"` 文档。
70
+
71
+ `findings` 为抑制和筛选后的结果;`hiddenLikely`、`baselineSuppressed`、`baselineStale` 提供相关统计。完整性需另查 `partial`、`errors`、`skipped`、`filesScanned`;SARIF 提供执行状态与诊断通知。
72
+
73
+ ## GitHub Action
74
+
75
+ 保存为 `.github/workflows/canship.yml`,在推送和 PR 时扫描并生成统计摘要。安装扫描器需要联网,扫描不联网;不安装或执行项目依赖,默认不上传 SARIF。
76
+
77
+ 示例固定 Action 提交,安装 npm 版 `0.2.1`;`version` 不使用仓库中的未发布源码。Action 兼容 0.2.1 无 `schemaVersion` 的报告。
78
+
79
+ ```yaml
80
+ name: canship
81
+ on: [push, pull_request]
82
+ permissions:
83
+ contents: read
84
+ jobs:
85
+ scan:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
89
+ with:
90
+ fetch-depth: 0
91
+ persist-credentials: false
92
+ - uses: Tasomei/canship@37cf06e014168968a88679157564e5c489f99c50
93
+ with:
94
+ version: '0.2.1'
95
+ ```
96
+
97
+ | 输入 | 默认值 | 说明 |
98
+ |---|---|---|
99
+ | `path` | `.` | 检出目录内的扫描路径 |
100
+ | `version` | `0.2.1` | 精确 npm 版本,不接受范围或标签 |
101
+ | `fail-on` | `blocking` | `blocking`:确定的 P0/P1;`any`:全部结果;`none`:仅报告结果 |
102
+ | `only` / `skip` | 未设置 | 互斥,逗号分隔的规则选择器 |
103
+ | `baseline` | 未设置 | 相对扫描目录的已有基线 |
104
+ | `use-config` | `false` | 启用项目配置 |
105
+ | `upload-sarif` | `false` | 上传 SARIF 至 GitHub 代码扫描 |
106
+ | `category` | `canship` | 每个扫描目标使用独立分类 |
107
+
108
+ 输出:`exit-code`、`findings`、`blocking`、`partial`。策略统计包含疑似结果,基线与忽略仍生效;扫描不完整、工具错误或报告不兼容始终失败,包括 `fail-on: none`。
109
+
110
+ 上传 SARIF 需 `security-events: write` 及 [GitHub 代码扫描支持](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,需要其他版本时应使用独立扫描任务。
111
+
112
+ ## 配置与基线
113
+
114
+ 扫描目录中的 `canship.config.json` 支持 `baseline`、`only`、`skip`、`all`:
115
+
116
+ ```json
117
+ {
118
+ "skip": ["cors/wildcard-with-credentials"],
119
+ "all": false
120
+ }
121
+ ```
122
+
123
+ 命令行参数优先;`only`、`skip` 互斥,接受规则 ID 或命名空间。未选规则不执行,`ruleSelection.removed` 仅统计已执行规则中被过滤的结果。扫描不可信项目时使用 `--no-config`;`bestEffort` 仅限命令行设置。
124
+
125
+ ### 忽略标记
126
+
127
+ 独占注释行的 `canship-ignore-file` 排除整个文件;`canship-ignore-next-line` 忽略下一行,可附规则 ID:
128
+
129
+ ```ts
130
+ // canship-ignore-next-line cors/wildcard-with-credentials
131
+ const corsOptions = { origin: '*', credentials: true }
132
+ ```
133
+
134
+ 报告披露忽略、规则筛选和基线抑制信息;主动排除不标记为未完成。
135
+
136
+ ### 基线
137
+
138
+ 记录已有结果:
139
+
140
+ ```powershell
141
+ npx canship --baseline-write
142
+ ```
143
+
144
+ 仅报告新增结果:
145
+
146
+ ```powershell
147
+ npx canship --baseline
148
+ ```
149
+
150
+ 默认基线位于扫描目录,显式路径相对工作目录。写入成功退出 `0`,不代表无问题;扫描不完整或启用规则筛选时会提示。
151
+
152
+ 基线格式为 v2,移动行号不改变指纹,替换凭据会改变。缺失、损坏及 v1 基线均退出 `3`。基线不含源码,但包含路径、规则和问题描述,提交前需审阅。
153
+
154
+ ## 隐私与限制
155
+
156
+ - 静态分析可能误报或漏报,不验证线上行为,不覆盖限流、注入、依赖漏洞或业务授权。无结果不等于安全。
157
+ - 脱敏仅覆盖已识别格式,未知秘密可能出现在源码摘录中。`--no-excerpts` 省略摘录,JSON 以 `excerptsOmitted` 标明;路径、名称、说明及基线不匿名化,分享前仍需审阅。
158
+ - Google/Firebase/Maps 的 `AIza...` 值按公开标识符处理,不单凭其值判定泄露。
159
+ - 读取上限:单文件 2 MiB,单次 128 MiB、10,000 个文件,目录 16 层。每文件跨规则最多 100 条结果,优先保留高严重度、高置信度结果。
160
+ - Git 历史每文件最多 100 个相关版本,单条 Git 命令超时 30 秒。超限、超时均报告检查缺口。
161
+ - 不跟随符号链接;嵌套仓库、子模块需单独扫描。范围内跳过项使扫描未完成,内置排除的构建和依赖目录除外。
162
+
163
+ ## 开发
164
+
165
+ 新增规则需包含应检出与不应检出的 [测试用例](./test/fixtures/)。
166
+
167
+ ```powershell
168
+ npm ci
169
+ ```
170
+
171
+ ```powershell
172
+ npm run prepublishOnly
173
+ ```
174
+
175
+ 离线评估:
176
+
177
+ ```powershell
178
+ npm run evaluate
179
+ ```
180
+
181
+ 评估集含 10 个构造用例、9 个固定版本上游示例及变体,同时纳入 `npm test`;[来源与许可](./test/fixtures/evaluation/) 随样本保存。断言覆盖规则、文件、严重度、置信度与扫描完整性,不代表真实项目检出率。
182
+
183
+ 另有 5 个 [应用目录快照](./test/evaluation/projects.json)。准备阶段联网并校验 Git 对象摘要,目标须为 Git 仓库外的新目录:
184
+
185
+ ```powershell
186
+ node scripts/fetch-evaluation-projects.mjs "$env:TEMP/canship-evaluation"
187
+ ```
188
+
189
+ 随后离线评估,不安装或运行样本依赖:
190
+
191
+ ```powershell
192
+ npm run evaluate:projects -- "$env:TEMP/canship-evaluation"
193
+ ```
194
+
195
+ CI 使用同一评估集,不验证 Git 历史或线上行为。
196
+
197
+ ## 许可
198
+
199
+ [MIT](./LICENSE)。Supabase、Firebase 样本保留 Apache-2.0,Next.js、`cors` 样本保留 MIT;各样本附来源及许可证。
package/README.md CHANGED
@@ -1,204 +1,199 @@
1
- # canship
2
-
3
- canship is a read-only static scanner for JavaScript and TypeScript web projects. It detects exposed credentials, private values carried into browser-delivered code by a public environment prefix (Next.js, Vite, Nuxt, Create React App, Expo, Gatsby, Vue CLI, SvelteKit), Supabase tables without Row Level Security, Firebase rules left open, and API routes that reach data without checking who is calling.
4
-
5
- That last check covers Next.js only — handlers under `app/api/**` and `pages/api/**`. A SvelteKit `+server.ts` or a Nuxt `server/api/` handler is scanned for everything else and is not checked for a missing authentication check. Every other check is framework-independent.
6
-
7
- The scan does not execute project code, upload content, or initiate network requests. The npm package has no runtime dependencies. When scanning a Git repository, canship reads only the local working tree and local commit history.
8
-
9
- ```bash
10
- npx canship
11
- ```
12
-
13
- Node.js 18 or later is required. Git is optional; only commit-history checks require a local Git installation and a readable repository. If canship is not already in the npm cache, `npx` may download it from the npm registry before the scan starts; that download is performed by npm and is not part of the scan.
14
-
15
- [简体中文](./README.zh-CN.md)
16
-
17
- ## Checks
18
-
19
- | Check | Risk | Severity |
20
- |---|---|---|
21
- | Hardcoded credentials | A scanned file contains a recognised OpenAI, Anthropic, AWS, Stripe, GitHub, npm, Slack, SendGrid, private-key, or database credential | P0 |
22
- | Private values in public environment variables | The value may be included in browser-delivered code | P0 |
23
- | Supabase `service_role` key reachable from client code | A `service_role` key can bypass Row Level Security (RLS) policies | P0 |
24
- | Git-tracked `.env` files | Recognised credentials remain in repository history; substantial unrecognised values may also produce a lower-confidence finding | P0 |
25
- | Supabase tables without RLS | A table exposed through the Supabase Data API lacks row-level access control | P1 |
26
- | Firebase rules allowing unconditional access | Unauthorised clients may be able to read or write data | P1 |
27
- | Next.js API routes accessing data without authentication | An unverified caller may access data or perform administrative operations | P0 / P1 |
28
- | CORS reflecting `Origin` while allowing credentials | Another site may access an endpoint with the user's credentials and read the response | P1 |
29
-
30
- Severity (P0, P1, P2) describes potential impact. Confidence (`certain`, `likely`) describes how strongly the code supports the finding. A `certain` P0/P1 finding blocks release and exits with status `1`; all other findings exit with status `2`.
31
-
32
- `Access-Control-Allow-Origin: *` combined with credentials is reported at P2 because browsers reject that configuration. A bare wildcard is not reported.
33
-
34
- For Git-tracked `.env` files, a recognised credential is reported at `certain` confidence. A substantial value that is not recognised but does not look public or placeholder-like is reported at `likely` confidence. Environment templates, public values, placeholders, and short settings do not trigger this Git rule solely because the file is tracked.
35
-
36
- ## Usage
37
-
38
- ```bash
39
- npx canship [path]
40
- ```
41
-
42
- The current directory is scanned when no path is provided.
43
-
44
- | Option | Description |
45
- |---|---|
46
- | `-a`, `--all` | Show `likely` findings |
47
- | `--json` | Write machine-readable JSON |
48
- | `--fix-prompt` | Write remediation instructions for a coding assistant |
49
- | `--report[=file]` | Write a self-contained HTML report; defaults to `canship-report.html` |
50
- | `--best-effort` | Allow exit `0` when the scan is incomplete and has no findings; does not change the status of existing findings |
51
- | `--baseline[=file]` | Hide findings recorded in the baseline, so only new ones are reported; defaults to `canship-baseline.json` |
52
- | `--baseline-write[=file]` | Record the current findings as a baseline and exit; defaults to `canship-baseline.json` |
53
- | `--only=ids` | Report only these rules; comma-separated and repeatable |
54
- | `--skip=ids` | Report everything except these rules; comma-separated and repeatable |
55
- | `--sarif[=file]` | Write a SARIF 2.1.0 log for CI code scanning; defaults to `canship.sarif` |
56
- | `--no-config` | Ignore `canship.config.json` in the scanned directory |
57
- | `-h`, `--help` | Show help |
58
- | `-v`, `--version` | Show the version |
59
-
60
- `--json` and `--fix-prompt` are mutually exclusive. `--report` writes a separate file and may be combined with either mode.
61
-
62
- ### Exit status
63
-
64
- | Status | Meaning |
65
- |---|---|
66
- | `0` | No findings at any confidence and the scan completed; with `--best-effort`, it may also mean an incomplete scan was accepted |
67
- | `1` | At least one `certain` P0/P1 finding |
68
- | `2` | Findings exist, but none is a confirmed P0/P1 release blocker |
69
- | `3` | Invalid arguments, a tool error, or an incomplete scan without `--best-effort` |
70
-
71
- When findings and an incomplete scan coexist, status `1` or `2` takes precedence. The JSON fields `partial`, `errors`, and `skipped` still preserve the incomplete-scan state.
72
-
73
- The default view expands only `certain` findings. Hidden `likely` findings still produce status `2`; the terminal and HTML report display a warning, and JSON reports the count in `hiddenLikely`. Use `--all` to include their full details.
74
-
75
- ### Excluding a file
76
-
77
- Add `canship-ignore-file` on a line by itself to exclude the entire file. The marker may be wrapped only in `//`, `#`, `--`, `*`, `/* */`, or `<!-- -->` comment syntax. Intentionally excluded files are listed in the report and do not make the scan incomplete.
78
-
79
- ### Excluding a single line
80
-
81
- Add `canship-ignore-next-line` on the line above a finding to suppress it there. The same comment syntax applies, and the marker must be the whole content of its line — a line that also holds code or prose does not suppress anything.
82
-
83
- ```ts
84
- // canship-ignore-next-line
85
- const documentedExample = "sk-proj-not-a-real-key"
86
- ```
87
-
88
- A bare marker suppresses every rule on the following line. A rule id after it narrows the suppression to that rule, so a line with one known false positive is not also blind to a different finding:
89
-
90
- ```ts
91
- // canship-ignore-next-line secrets/hardcoded/openai
92
- const key = process.env.OPENAI_KEY
93
- ```
94
-
95
- Rule ids appear in `--json` output; the terminal and HTML reports do not print them, which is why the bare form exists.
96
-
97
- The marker governs the line immediately after it, with no allowance for blank lines. Suppressed findings are listed by file, line, and rule in the terminal, the HTML report, and the `ignoredFindings` field of `--json`. They do not make the scan incomplete, and the report states that the result is not finding-free.
98
-
99
- ### Configuration
100
-
101
- Settings a project makes once can be committed to `canship.config.json` in the scanned directory. A command-line flag always overrides the file.
102
-
103
- ```json
104
- {
105
- "baseline": "canship-baseline.json",
106
- "skip": ["cors/wildcard-with-credentials"],
107
- "all": false
108
- }
109
- ```
110
-
111
- | Setting | Equivalent flag |
112
- |---|---|
113
- | `baseline` | `--baseline=file` |
114
- | `only` | `--only=ids` |
115
- | `skip` | `--skip=ids` |
116
- | `all` | `--all` |
117
-
118
- The format is JSON and not JavaScript. A `canship.config.js` would be project code, and the scan does not execute project code.
119
-
120
- An unknown setting, a wrong type, a rule id that names no rule, or `only` and `skip` together are all errors and exit `3`. A missing config file is not an error. A `baseline` path must stay inside the scanned project; the `--baseline` flag is not restricted that way.
121
-
122
- **The config file comes out of the directory being scanned.** When that directory is code you control, this is the point of the feature. When it is not — a dependency, a fork, an unreviewed pull request — the project can use it to switch off the rules that would report it. `--no-config` ignores the file entirely.
123
-
124
- `bestEffort` is deliberately not a setting. It turns an incomplete scan from exit `3` into exit `0`, and that is a judgement for whoever runs canship rather than a property of the project being scanned. Naming it in the file is an error rather than something quietly ignored. Use `--best-effort`.
125
-
126
- ### Selecting rules
127
-
128
- `--only` and `--skip` take the rule ids that appear in `--json` output, comma-separated and repeatable. A selector matches an id exactly, or matches every id beneath it at a `/` boundary — `secrets` covers every credential format, `secrets/hardcoded/openai` covers one. A partial id such as `secrets/hardcoded/open` matches nothing and is rejected, so a mistyped id cannot quietly disable a rule.
129
-
130
- `--only` and `--skip` cannot be combined. Selection filters findings rather than skipping the rules themselves, so it does not reduce scan time. Every report states which selection was in force and how many findings it hid.
131
-
132
- ### Baseline
133
-
134
- An existing project usually has findings on the first run. A baseline records them so that subsequent runs report only what appeared afterwards, which is what makes canship usable in continuous integration for a project that did not start with it.
135
-
136
- ```bash
137
- npx canship --baseline-write # accept the current findings
138
- npx canship --baseline # report only new ones
139
- ```
140
-
141
- `--baseline-write` writes `canship-baseline.json` and exits `0` without scanning further. Commit that file: it is the record of what was accepted, and it is meant to be reviewed in the pull request that adds it.
142
-
143
- **Consider what committing it publishes.** Each entry names a file path, a rule id, and a finding title, and every entry describes a problem that has not been fixed. canship searches gitignored credential files by design, so an entry may describe a file the repository does not contain — `.env.local` and the name of a variable in it, for example. The file holds no credential values. On a private repository this is the intended use; on a public one, weigh the disclosure before committing.
144
-
145
- Paths are resolved where they came from: a path typed on the command line is relative to the working directory, a bare `--baseline` or `--baseline-write` uses the scanned project's own `canship-baseline.json`, and a path in `canship.config.json` is relative to that file. So `npx canship ./app --baseline-write` writes into `./app`, which is where `npx canship ./app --baseline` then looks.
146
-
147
- A baseline entry is matched by rule, file, title, and a digest of its original source evidence; only findings without locatable evidence fall back to the excerpt. The source digest is computed before redaction and truncation, so replacing a credential cannot hide behind the old display text. The line number is deliberately excluded, so editing a file above a line-based finding does not report it as new. Each entry carries the number of times it was seen; an additional occurrence beyond that count is reported.
148
-
149
- The baseline format is now version 2, and the SARIF fingerprint is named `canshipFindingV2`. Older baselines are rejected with exit code `3`, never automatically converted or overwritten. Review the findings again before regenerating with `--baseline-write`.
150
-
151
- The baseline stores a SHA-256 hash rather than the excerpt itself, because the file is intended to be committed and canship cannot guarantee that an unrecognised credential is masked.
152
-
153
- A baseline hides real findings. Every output states how many:
154
-
155
- - the terminal and HTML report never show a clean result while a baseline is suppressing findings, and name the count and the file
156
- - `--json` reports `baselineSuppressed`
157
- - baseline entries that no longer match anything are reported as `baselineStale`; they do not affect the exit status
158
-
159
- Findings of every confidence are recorded. A baseline that cannot be read — missing, malformed, or written by a different format version — exits `3` rather than proceeding with no suppression. `--baseline` and `--baseline-write` cannot be combined.
160
-
161
- ### Remediation instructions
162
-
163
- `--fix-prompt` separates code changes that a coding assistant can perform from actions that require the project maintainer, such as rotating credentials, changing provider settings, or rewriting Git history.
164
-
165
- ## Rule activation
166
-
167
- | Rule | Activation conditions |
168
- |---|---|
169
- | Public environment variables | The name starts with `NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, `EXPO_PUBLIC_`, `NUXT_PUBLIC_`, `GATSBY_`, `VUE_APP_`, or `PUBLIC_` |
170
- | Git-tracked environment files | The target is inside a readable local Git repository whose `.git` metadata is inside the checkout or names it back; templates, public values, placeholders, and short settings are excluded, and at most the 100 most recent relevant revisions of each file are inspected |
171
- | Next.js API authentication | Only data-accessing handlers under `app/api/**` and `pages/api/**`; recognises enforcing authentication calls, identity conditions that control rejection, and middleware `matcher` coverage for the route |
172
- | Supabase RLS | The current project scope contains `supabase/`, an `@supabase/supabase-js` or `@supabase/ssr` import, or a `SUPABASE_URL`; table-related DDL is replayed across migrations within that project scope |
173
- | Other credential and configuration rules | Match known file formats and content patterns without requiring a specific front-end framework |
174
-
175
- ## Known limitations
176
-
177
- - canship uses static heuristics and does not verify runtime behaviour. Custom authentication wrappers, dynamic configuration, and unsupported syntax can cause false positives or false negatives.
178
- - Detection and redaction use the same credential patterns. A credential that canship cannot recognise cannot be guaranteed to be masked; if another rule quotes the same line, the original value may appear in the report. Treat reports as internal material.
179
- - Entropy-based detection is intentionally not used because random identifiers, hashes, and ordinary Base64 text cannot be classified reliably from entropy alone.
180
- - Files are limited to 2 MiB, traversal to 16 directory levels, and output to 100 findings per file. Git history checks inspect at most the 100 most recent relevant revisions of each file. Reaching a limit is recorded explicitly.
181
- - Symbolic links are not followed and make the scan incomplete.
182
- - Nested Git repositories and submodules are not expanded by the parent scan. They are listed as skipped and the parent scan is marked incomplete.
183
- - A `.git` file may point outside the checkout. canship follows it only when the target names this checkout back, which is what git records for a linked worktree and for a submodule. Anything else is read as a redirect to an unrelated repository: file scanning continues, history checks are marked incomplete.
184
- - Google, Firebase, and Maps `AIza...` keys are treated as public identifiers. Their application and API restrictions exist in Google Cloud and cannot be verified from local source, so the key value alone is not reported as a credential leak.
185
- - Rate limiting, injection, dependency vulnerabilities, and business authorisation beyond caller authentication are outside the scan scope.
186
-
187
- A canship result describes only what the implemented rules observed in the files that were read. It is not proof that the project has no other security defects.
188
-
189
- ## Planned
190
-
191
- `--probe` is not implemented. The planned mode would, after explicit confirmation, issue read-only verification requests to service endpoints found in local project configuration. The current release does not initiate network requests.
192
-
193
- ## Contributing
194
-
195
- Every new or changed detection rule should include at least two fixtures: one that must be reported and one that must not. See [`test/fixtures/`](./test/fixtures/).
196
-
197
- ```bash
198
- npm ci
199
- npm run prepublishOnly
200
- ```
201
-
202
- ## License
203
-
204
- [MIT](./LICENSE)
1
+ # canship
2
+
3
+ A local static scanner for JavaScript and TypeScript projects. Detects exposed credentials and access-control misconfigurations. Scans do not execute project code, upload files, or use the network.
4
+
5
+ This documentation covers 0.3.x: use a matching [npm version](https://www.npmjs.com/package/canship) or local build.
6
+
7
+ ```powershell
8
+ npx canship .
9
+ ```
10
+
11
+ Requires Node.js ≥18; no runtime dependencies. `npx` may download the package; scans use only local files and Git history. Unavailable Git in a repository marks the scan incomplete.
12
+
13
+ [简体中文](./README-zh-CN.md)
14
+
15
+ ## Checks
16
+
17
+ | Check | Severity |
18
+ |---|---|
19
+ | Hardcoded credentials, private keys, and database URLs containing passwords | P0 |
20
+ | Private values in public environment variables | P0 |
21
+ | Supabase admin keys exposed to clients | P0 |
22
+ | Credentials or suspected private values in Git-tracked and historical `.env` files | P0 |
23
+ | Supabase tables without Row Level Security (RLS) in migrations | P1 |
24
+ | Firebase unconditional access and date-based test rules | P1 |
25
+ | Next.js API data operations without recognised authentication | P0 / P1 |
26
+ | Credentialed CORS with reflected or wildcard origins | P1 / P2 |
27
+
28
+ Supports OpenAI, Anthropic, AWS, Stripe, GitHub, npm, Slack, SendGrid, and other credential formats, plus common frontend public environment prefixes. API authentication checks cover only Next.js `/api`: App Router, Pages Router, route groups, and workspace applications.
29
+
30
+ Confidence is `certain` or `likely`, describing static evidence rather than credential validity or deployed state. Only certain findings are shown by default; hidden likely findings still affect the exit code.
31
+
32
+ ## Usage
33
+
34
+ Omitting the path scans the current directory.
35
+
36
+ | Option | Description |
37
+ |---|---|
38
+ | `-a`, `--all` | Include likely findings |
39
+ | `--json` | Output JSON |
40
+ | `--fix-prompt` | Output remediation instructions and separate manual actions |
41
+ | `--report[=file]` | Write HTML; default: `canship-report.html` |
42
+ | `--sarif[=file]` | Write SARIF 2.1.0; default: `canship.sarif` |
43
+ | `--best-effort` | Permit exit `0` for an incomplete scan with no findings |
44
+ | `--baseline[=file]` | Apply a baseline; default: `canship-baseline.json` |
45
+ | `--baseline-write[=file]` | Write current findings as a baseline and exit; same default path |
46
+ | `--only=ids` | Run matching rules; comma-separated and repeatable |
47
+ | `--skip=ids` | Exclude matching rules; comma-separated and repeatable |
48
+ | `--no-config` | Ignore project configuration |
49
+ | `--list-rules` | List rules and limits without scanning; supports `--json` |
50
+ | `--no-excerpts` | Omit source excerpts from every report; preserve findings and exit status |
51
+ | `-h`, `--help` | Show help |
52
+ | `-v`, `--version` | Show version |
53
+
54
+ `--json` and `--fix-prompt` are mutually exclusive; HTML and SARIF work with either. Reports are in English. `--all` applies to every format.
55
+
56
+ ### Exit codes
57
+
58
+ | Code | Meaning |
59
+ |---|---|
60
+ | `0` | No findings, with a complete scan or an incomplete scan accepted by `--best-effort` |
61
+ | `1` | At least one certain P0/P1 finding |
62
+ | `2` | Other findings, including hidden likely findings |
63
+ | `3` | Invalid arguments, a tool error, or an unaccepted incomplete scan |
64
+
65
+ Finding exit codes take precedence over incompleteness; `--best-effort` does not change `1` or `2`.
66
+
67
+ ### Machine-readable output
68
+
69
+ JSON uses `schemaVersion: 1`, independent of the package version; the npm package includes its [schema](./schemas/scan-report-v1.schema.json). Accept additive fields and reject unsupported schema versions. `--list-rules --json` is a separate `kind: "rule-catalog"` document.
70
+
71
+ `findings` contains results after suppressions and filtering; `hiddenLikely`, `baselineSuppressed`, and `baselineStale` provide related counts. Check coverage separately through `partial`, `errors`, `skipped`, and `filesScanned`. SARIF includes execution status and diagnostic notifications.
72
+
73
+ ## GitHub Action
74
+
75
+ Save as `.github/workflows/canship.yml` to scan on pushes and pull requests, with a counts-only summary. Scanner installation requires network access; scanning does not. Project dependencies are not installed or executed, and SARIF upload is disabled by default.
76
+
77
+ The example pins the Action commit and installs npm version `0.2.1`; `version` does not use unreleased repository source. The Action accepts 0.2.1 reports without `schemaVersion`.
78
+
79
+ ```yaml
80
+ name: canship
81
+ on: [push, pull_request]
82
+ permissions:
83
+ contents: read
84
+ jobs:
85
+ scan:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
89
+ with:
90
+ fetch-depth: 0
91
+ persist-credentials: false
92
+ - uses: Tasomei/canship@37cf06e014168968a88679157564e5c489f99c50
93
+ with:
94
+ version: '0.2.1'
95
+ ```
96
+
97
+ | Input | Default | Meaning |
98
+ |---|---|---|
99
+ | `path` | `.` | Directory within the checkout |
100
+ | `version` | `0.2.1` | Exact npm version; no ranges or tags |
101
+ | `fail-on` | `blocking` | `blocking`: certain P0/P1; `any`: all findings; `none`: findings only reported |
102
+ | `only` / `skip` | unset | Mutually exclusive, comma-separated rule selectors |
103
+ | `baseline` | unset | Existing baseline relative to the scanned directory |
104
+ | `use-config` | `false` | Enable project configuration |
105
+ | `upload-sarif` | `false` | Upload SARIF to GitHub code scanning |
106
+ | `category` | `canship` | Distinct SARIF category for each scan target |
107
+
108
+ Outputs: `exit-code`, `findings`, `blocking`, `partial`. Policies include likely findings; baselines and exclusions still apply. Incomplete scans, tool errors, and incompatible reports always fail, including with `fail-on: none`.
109
+
110
+ SARIF upload requires `security-events: write` and [GitHub code scanning support](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/integrate-with-existing-tools/upload-sarif-file); fork PRs may lack permission. Review paths and finding details before upload. Use `pull_request`, not `pull_request_target`, for untrusted PRs. The Action sets Node.js 22 for subsequent steps; use a separate scan job if another version is needed.
111
+
112
+ ## Configuration and baselines
113
+
114
+ Place `canship.config.json` in the scanned directory. Supported keys: `baseline`, `only`, `skip`, `all`.
115
+
116
+ ```json
117
+ {
118
+ "skip": ["cors/wildcard-with-credentials"],
119
+ "all": false
120
+ }
121
+ ```
122
+
123
+ CLI options take precedence. `only` and `skip` are mutually exclusive and accept rule IDs or namespaces. Unselected rules do not run; `ruleSelection.removed` counts filtered findings only from executed rules. Use `--no-config` for untrusted projects. `bestEffort` is CLI-only.
124
+
125
+ ### Ignore markers
126
+
127
+ A standalone `canship-ignore-file` comment excludes a file; `canship-ignore-next-line` suppresses the next line, with an optional rule ID:
128
+
129
+ ```ts
130
+ // canship-ignore-next-line cors/wildcard-with-credentials
131
+ const corsOptions = { origin: '*', credentials: true }
132
+ ```
133
+
134
+ Reports disclose exclusions, rule selection, and baseline suppression. Deliberate exclusions do not mark the scan incomplete.
135
+
136
+ ### Baselines
137
+
138
+ Record existing findings:
139
+
140
+ ```powershell
141
+ npx canship --baseline-write
142
+ ```
143
+
144
+ Report only new findings:
145
+
146
+ ```powershell
147
+ npx canship --baseline
148
+ ```
149
+
150
+ The default baseline is in the scanned directory; explicit paths are relative to the working directory. A successful write exits `0`, regardless of findings; incomplete or selectively scanned input produces a warning.
151
+
152
+ Baseline format v2 fingerprints survive line moves but change when credentials change. Missing, malformed, and v1 baselines exit `3`. Baselines omit source but retain paths, rules, and issue descriptions; review before committing.
153
+
154
+ ## Privacy and limitations
155
+
156
+ - Static analysis may produce false positives or negatives. Deployed behaviour, rate limiting, injection, dependency vulnerabilities, and business authorisation are outside scope. No findings does not prove security.
157
+ - Redaction covers recognised formats only; unknown secrets may appear in source excerpts. `--no-excerpts` omits excerpts, recorded as `excerptsOmitted` in JSON. Paths, names, descriptions, and baselines are not anonymised; review before sharing.
158
+ - Google/Firebase/Maps `AIza...` values are public identifiers, not evidence of a leak on their own.
159
+ - Read limits: 2 MiB per file, 128 MiB and 10,000 files per scan, 16 directory levels. At most 100 findings per file across rules, prioritising severity and confidence.
160
+ - Git history checks cover up to 100 relevant revisions per file, with a 30-second timeout per Git command. Exceeded limits and timeouts report coverage gaps.
161
+ - Symbolic links are not followed; scan nested repositories and submodules separately. Skipped paths within scope mark the scan incomplete; built-in build and dependency exclusions do not.
162
+
163
+ ## Development
164
+
165
+ New rules require positive and negative [test cases](./test/fixtures/).
166
+
167
+ ```powershell
168
+ npm ci
169
+ ```
170
+
171
+ ```powershell
172
+ npm run prepublishOnly
173
+ ```
174
+
175
+ Offline evaluation:
176
+
177
+ ```powershell
178
+ npm run evaluate
179
+ ```
180
+
181
+ The corpus has 10 synthetic cases and nine pinned upstream examples and mutations, also run by `npm test`. [Sources and licences](./test/fixtures/evaluation/) accompany the fixtures. Assertions cover rules, files, severity, confidence, and coverage, not real-world detection rates.
182
+
183
+ Five [application-directory snapshots](./test/evaluation/projects.json) provide additional evaluation. Preparation uses the network and verifies Git object hashes; the target must be a new directory outside Git:
184
+
185
+ ```powershell
186
+ node scripts/fetch-evaluation-projects.mjs "$env:TEMP/canship-evaluation"
187
+ ```
188
+
189
+ Then evaluate offline without installing or running sample dependencies:
190
+
191
+ ```powershell
192
+ npm run evaluate:projects -- "$env:TEMP/canship-evaluation"
193
+ ```
194
+
195
+ CI uses the same corpus. Git history and deployed behaviour are outside this evaluation.
196
+
197
+ ## License
198
+
199
+ [MIT](./LICENSE). Supabase and Firebase fixtures retain Apache-2.0; Next.js and `cors` fixtures retain MIT. Each includes its source and licence.