canship 0.7.1 → 0.8.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.
- package/README-zh-CN.md +58 -141
- package/README.md +58 -141
- package/dist/cli.js +3809 -665
- package/dist/index.d.ts +78 -8
- package/dist/index.js +1127 -72
- package/docs/framework-support-zh-CN.md +95 -0
- package/docs/framework-support.md +95 -0
- package/docs/reference-zh-CN.md +221 -0
- package/docs/reference.md +221 -0
- package/package.json +13 -2
- package/schemas/config-v1.schema.json +114 -0
- package/schemas/scan-report-v1.schema.json +19 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Canship reference
|
|
2
|
+
|
|
3
|
+
[Overview](../README.md) · [简体中文](./reference-zh-CN.md)
|
|
4
|
+
|
|
5
|
+
This reference covers `0.8.0`. Other versions are listed under [Releases](https://github.com/Tasomei/canship/releases).
|
|
6
|
+
|
|
7
|
+
## Server entry points
|
|
8
|
+
|
|
9
|
+
[Framework coverage and regression cases](./framework-support.md) detail entry recognition, authentication, request inputs and analysis boundaries.
|
|
10
|
+
|
|
11
|
+
| Framework | Entry points |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Next.js | App Router handlers, Pages Router `/api`, `'use server'` functions |
|
|
14
|
+
| SvelteKit | `+server` endpoints and `+page.server` form actions |
|
|
15
|
+
| Nuxt | `server/api`, `server/routes` |
|
|
16
|
+
| Remix / React Router | `loader` and `action` exports in `app/routes` |
|
|
17
|
+
| Astro | Endpoints in `src/pages` |
|
|
18
|
+
| Express | `app`/`Router` routes, including `.route()` chains, mounted routers, and controllers in other files |
|
|
19
|
+
| Hono | Method routes, `OpenAPIHono.openapi()` / `openapiRoutes()`, chains, `basePath`, and `app.route()` sub-apps |
|
|
20
|
+
| Fastify | Shorthand and `route()` declarations, `register()` prefixes and encapsulation, `@fastify/autoload` directories |
|
|
21
|
+
|
|
22
|
+
Recognised Next.js/Astro middleware may suppress covered auth findings; Server Functions need local checks. Express/Hono/Fastify require resolved rejection logic or known auth libraries. Fastify decorator and plugin evidence is scoped to the local instance.
|
|
23
|
+
|
|
24
|
+
For Express/Hono/Fastify, conditional middleware cannot protect registrations outside its branch or function. Literal `true`/`false` branches and simple boolean short circuits are recognised; other conditions are not evaluated. Distinct helper-call contexts retain their own middleware evidence. This is bounded static registration analysis, not general control-flow execution.
|
|
25
|
+
|
|
26
|
+
Project router factories support top-level `const app = make()` calls when a synchronous, zero-argument function returns a fresh Express, Hono/OpenAPIHono or Fastify instance. Static ESM imports, named/default re-exports and a single `export *` source are followed. Literal Hono `basePath()` prefixes are retained; middleware remains instance-local. Conditional or async returns, parameters, shared instances, mutation and arbitrary wrapper chains are not inferred. Resolution limits mark coverage incomplete; unsupported syntax may remain outside route discovery.
|
|
27
|
+
|
|
28
|
+
Factory imports use the nearest `tsconfig.json` / `jsconfig.json` for single-target `paths` mappings with `baseUrl`, comments and trailing commas. Configuration inheritance and project references remain unresolved. Workspace resolution requires an explicit `dependencies` link (`workspace:*`, `workspace:^` or `workspace:~`) and a matching package in a `package.json` workspace list; patterns allow one segment wildcard. Only declared export subpaths are followed. Runtime export conditions must converge on one source; type-only branches, differing targets, duplicate package names and registry version ranges cannot establish that source. These mappings identify source candidates, not deployment behaviour: [TypeScript paths](https://www.typescriptlang.org/tsconfig/paths.html) do not rewrite runtime imports; [package exports](https://nodejs.org/api/packages.html#conditional-exports) may depend on the environment.
|
|
29
|
+
|
|
30
|
+
Session checks and webhook verification (Stripe, Polar, Clerk, Svix, QStash) must reject failures; asynchronous calls must be awaited or returned. Indirect evidence from project helpers/wrappers, SvelteKit hooks, or Nuxt middleware may lower confidence. Unresolved auth sources do not suppress findings.
|
|
31
|
+
|
|
32
|
+
Input analysis follows assignments, destructuring, string construction, and resolvable cross-file passthrough helpers. Helper names alone do not prove sanitisation.
|
|
33
|
+
|
|
34
|
+
For Express, Hono, and Fastify routes, writes inside called project functions are followed two levels (handler → service → model); file-based routes report writes in the route file only. SvelteKit page loads and remote functions are outside route analysis. Content-based checks, including credentials and CORS, still apply.
|
|
35
|
+
|
|
36
|
+
OpenAPI configuration supports inline objects, constants, and static ESM imports/re-exports, with literal paths and at most eight resolution steps. Middleware retains its defining file. Dynamic configuration, detected mutations, and multiple `export *` sources cannot prove protection; arbitrary module side effects are not modelled. `security` declarations and validation hooks are not authentication.
|
|
37
|
+
|
|
38
|
+
`openapiRoutes()` supports static arrays, spreads, and `defineOpenAPIRoute()` entries. Only literal `addRoute: false` skips an entry. Unresolved entries or handlers mark coverage incomplete when route-based checks are selected.
|
|
39
|
+
|
|
40
|
+
## CLI
|
|
41
|
+
|
|
42
|
+
`npx canship [path] [options]`
|
|
43
|
+
|
|
44
|
+
Terminal report layout adapts down to 24 columns, accounting for common CJK characters and emoji. Narrow views stack counts and separate command labels from copyable commands. Paths, excerpts, code examples and commands retain complete logical lines; the terminal may soft-wrap them. Glyph widths can vary by terminal and font. Redirected output is uncoloured by default; `FORCE_COLOR=0` disables colour, and nonempty `NO_COLOR` takes precedence. Windows follow-up commands use PowerShell quoting.
|
|
45
|
+
|
|
46
|
+
| Option | Effect |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `-a`, `--all` | Include `likely` findings |
|
|
49
|
+
| `--verbose` | Expand terminal findings |
|
|
50
|
+
| `--no-progress` | Disable interactive stderr progress; structured output and redirected streams stay quiet |
|
|
51
|
+
| `--report[=file]` | Write HTML; default `canship-report.html` |
|
|
52
|
+
| `--open` | Open `--report` output; disabled in CI and non-interactive shells |
|
|
53
|
+
| `--json` | Print JSON |
|
|
54
|
+
| `--probe=url` | Preview a deployment probe without DNS or HTTP requests |
|
|
55
|
+
| `--confirm-probe=hash` | Execute only the matching reviewed probe plan |
|
|
56
|
+
| `--probe-expect-auth` | Review HEAD/canary responses other than the requested 401/403 rejection |
|
|
57
|
+
| `--probe-canary-sha256=hash` | Add a bounded GET of a dedicated synthetic canary |
|
|
58
|
+
| `--workspace=path` | Scan explicit subprojects independently; repeatable, up to 32; terminal or JSON output |
|
|
59
|
+
| `--compare=before.json` + `--with=after.json` | Compare saved scan reports; supports `--json` and `--report` |
|
|
60
|
+
| `--share-summary` | Print counts and scope flags without project text; supports `--json`, never uploads |
|
|
61
|
+
| `--sarif[=file]` | Write SARIF 2.1.0; default `canship.sarif` |
|
|
62
|
+
| `--fix-prompt` | Print repair instructions and separate manual actions |
|
|
63
|
+
| `--no-excerpts` | Remove excerpts from all reports |
|
|
64
|
+
| `--changed-since=ref` | Show changed-file findings; preserve full-scan status |
|
|
65
|
+
| `--only=ids` / `--skip=ids` | Select/exclude rules or namespaces; comma-separated, repeatable |
|
|
66
|
+
| `--exclude=path` | Exclude a literal project-relative file or directory; repeatable |
|
|
67
|
+
| `--list-rules` | List rules without scanning; supports `--only` / `--skip` and `--json` |
|
|
68
|
+
| `--explain-config` | Show effective settings, sources, and selected rules without scanning; supports `--json` |
|
|
69
|
+
| `--doctor` | Run read-only environment checks; supports `--json`, `--no-config`, `--baseline`, and output-path checks |
|
|
70
|
+
| `--init[=config\|ci\|ci-workspaces\|pre-commit]` | Preview config, CI or hook templates; no file changes |
|
|
71
|
+
| `--baseline[=file]` / `--baseline-write[=file]` | Suppress/record findings; default `canship-baseline.json` |
|
|
72
|
+
| `--baseline-migrate[=file]` | Print a migrated baseline as JSON; preserve the source file |
|
|
73
|
+
| `--baseline-review` | Compare accepted and current findings; supports `--baseline[=file]` and `--json` |
|
|
74
|
+
| `--baseline-prune` | Print a v4 candidate retaining only active, matched acceptances; preserve the source |
|
|
75
|
+
| `--baseline-accept=ids` | Print a candidate accepting selected `fingerprint[:count]` entries; default count `1`, comma-separated and repeatable |
|
|
76
|
+
| `--baseline-reason=text` / `--baseline-expires=UTC` | With `--baseline-accept`, record a reason and/or UTC expiry |
|
|
77
|
+
| `--no-config` / `--no-ignore-markers` | Ignore project configuration/source suppression comments |
|
|
78
|
+
| `--best-effort` | Allow incomplete coverage with no findings to exit `0` |
|
|
79
|
+
| `-h`, `--help` / `-v`, `--version` | Show help/version |
|
|
80
|
+
| `--build-info` | Show channel, source revision, dirty state and capabilities; supports `--json` |
|
|
81
|
+
|
|
82
|
+
`--json` and `--fix-prompt` are mutually exclusive; either supports HTML and SARIF output.
|
|
83
|
+
|
|
84
|
+
Repeat `--workspace=apps/web --workspace=apps/admin` to scan selected directories separately. Paths must be literal, non-overlapping, and free of symlink components. Each project uses its own config and baseline; parent config and unselected sources are not inherited. CLI rule, exclusion, visibility, and privacy options override each project's settings; bare `--baseline` selects each project's default file. Results include configuration sources, per-project coverage, and full-confidence counts. A failed project makes the batch exit `3`; otherwise normal finding precedence applies. Only terminal and JSON (`kind: "workspace-report"`) are supported; use individual scans for HTML/SARIF or baseline maintenance.
|
|
85
|
+
|
|
86
|
+
`--compare` reads two local v1 JSON reports (up to 10 MiB and 50,000 findings each). It lists added, persisting, and no-longer-observed records using stable identities and counts; missing source digests remain unpaired. Coverage gaps, filters, baselines, different roots, and changed or unverifiable scanner builds limit comparison. Exit `0` means no known comparison limitation, `2` means limited comparison, and `3` means invalid input or output failure—not the scan's release policy. Absence is not proof of remediation. Titles, excerpts and scan roots are omitted; finding paths remain. No source scan or project-code execution occurs. JSON uses `kind: "report-comparison"`.
|
|
87
|
+
|
|
88
|
+
Comparison output is read-only by default. `--report` explicitly writes an offline HTML view to `canship-comparison.html` in the working directory; `--report=file.html` selects another path and can accompany `--json`. Inputs and unrelated files are not overwritten. HTML shows up to 2,000 detail rows and 512 UTF-16 code units per path/rule reference, disclosing truncation; all aggregate counts and the comparison exit status are retained. Use JSON for full references. Other scan and output modes, including `--open`, remain unavailable in comparison mode.
|
|
89
|
+
|
|
90
|
+
`--share-summary` counts all confidence levels after rule selection, source suppressions, and baselines, with the normal scan exit status. It omits paths, titles, identifiers, excerpts, and diagnostic details; handled failures show only a code and local troubleshooting advice. Detailed reports and changed-file views cannot be combined with it. JSON uses `kind: "share-summary"`, not the scan-report schema. Counts may still be sensitive; review before sharing.
|
|
91
|
+
|
|
92
|
+
`--init` is a standalone preview: stdout contains the template; stderr names its intended destination. Review before saving. CI previews use the scanner's package version; confirm that version is published and review the Action pin before enabling the workflow.
|
|
93
|
+
|
|
94
|
+
`--init=ci-workspaces` previews a matrix with independent jobs, `fail-fast: false`, and distinct SARIF categories. Replace sample paths and names before use. Project configuration and SARIF upload remain disabled; enabling upload also requires the appropriate permissions.
|
|
95
|
+
|
|
96
|
+
`--init=pre-commit` previews a Node hook, without installing it or changing Git settings. Review before saving as `pre-commit` in the [Git hooks directory](https://git-scm.com/docs/githooks); make it executable where required. Set `CANSHIP_CLI` to a trusted, separately installed `dist/cli.js` outside the worktree; its version must match the template. The hook scans the full working tree, including unstaged changes, with all findings visible and project suppressions disabled. It does not validate the staged snapshot: review staged-only content separately. Every nonzero exit blocks the commit; the hook makes no downloads and times out after two minutes of scanning.
|
|
97
|
+
|
|
98
|
+
`--changed-since` compares the local merge base with the working tree, including non-ignored untracked files. It does not fetch or narrow scan scope. Missing Git, refs, or shared history exits `3`; it cannot be combined with `--baseline-write`.
|
|
99
|
+
|
|
100
|
+
`--doctor` checks Node.js, directory access, configuration, baseline structure, and local Git metadata. In this mode, `--report` / `--sarif` only check destinations; no reports or test files are written. Exit `3` indicates preflight errors; `0` may include warnings and does not establish scan coverage, baseline matches, or successful future writes. JSON uses `kind: "doctor"`; diagnostics omit project content, baseline entries, environment variables, and remote addresses.
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
`canship.config.json` accepts `baseline`, `only`, `skip`, `exclude`, and `all`. CLI options take precedence; `only` and `skip` are mutually exclusive.
|
|
105
|
+
|
|
106
|
+
For editor completion, set `$schema` to the bundled [configuration schema](../schemas/config-v1.schema.json), e.g. `./node_modules/canship/schemas/config-v1.schema.json` after local installation. Canship does not fetch this reference. Invalid fields include a field path and line/column; JSON syntax errors include a location when available. Schema validation does not verify baseline files or path containment.
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{ "skip": ["cors/wildcard-with-credentials"], "all": false }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`--explain-config` resolves the same settings as a scan. It does not read source or baseline contents, check Git history, or write files. Exit `0` confirms configuration resolution, not scan coverage or baseline validity. JSON uses `kind: "effective-config"`, not the scan-report schema. Paths remain visible; review output before sharing. Report output, baseline writes/migration, and `--changed-since` cannot be combined with this mode.
|
|
113
|
+
|
|
114
|
+
A standalone `canship-ignore-file` comment excludes a file. `canship-ignore-next-line [rule]` suppresses the next line, optionally for one rule. Exclusions are disclosed and may reduce status to `0` without marking coverage incomplete. For untrusted projects, use `--no-config --no-ignore-markers`.
|
|
115
|
+
|
|
116
|
+
`exclude` uses case-sensitive literal paths, such as `generated/` or `test/fixtures/`; no globs or traversal. Up to 64 paths of 512 characters are accepted. CLI paths replace the configuration list; `--no-config` disables project-provided exclusions. Matching file contents and environment-history objects are not read; configuration, baseline, and Git metadata reads are separate. Reports disclose requested and matched exclusion paths, not excluded file counts. Baseline maintenance refuses this restricted scope.
|
|
117
|
+
|
|
118
|
+
Baselines accept existing findings without fixing them. New baselines use v4 with optional reasons and expiry; v2/v3 remain readable. The v3 fingerprint algorithm is unchanged: title, language, and line moves do not change identity; source evidence does. SARIF retains v2 and v3 fingerprints. Older scanners reject v4 instead of ignoring expiry.
|
|
119
|
+
|
|
120
|
+
`--baseline-review` lists retained, unmatched, unaccepted, and expired records; unmatched does not mean fixed. Review and acceptance start empty only when the implicit default baseline is absent; explicit or configured missing files fail. Prune and acceptance require complete, unfiltered coverage without source suppressions. Both print candidates without changing the source: pruning accepts nothing new; acceptance adds only selected counts and preserves old v3/v4 records. Select full fingerprints from the review, then save the reviewed candidate to a different file. v2 acceptance requires all old entries to match. These commands use operation status, not finding severity; incomplete reviews exit `3`.
|
|
121
|
+
|
|
122
|
+
Reasons are optional, limited to 500 characters, and should contain no secrets or personal data. Expiry requires a future UTC timestamp such as `2030-01-01T00:00:00Z`; records stop suppressing at that instant. Different decisions for the same fingerprint keep separate counts and expiry. Reports disclose expired counts; review shows the reason and deadline. Omitting expiry creates a permanent acceptance.
|
|
123
|
+
|
|
124
|
+
`--baseline-migrate` requires a complete, unfiltered scan and matching, unexpired entries. It accepts no new findings, prints v4 JSON, and leaves the old file unchanged. Review/prune unmatched or expired decisions first. Reports and baselines are written atomically; existing unrelated files and symbolic-link targets are not overwritten.
|
|
125
|
+
|
|
126
|
+
Default paths are relative to the scan directory; explicit paths are relative to the working directory. Read/write modes are mutually exclusive. Missing, invalid, or v1 baselines exit `3`. A successful write exits `0`, with a warning for incomplete or selective scans.
|
|
127
|
+
|
|
128
|
+
## Deployment probes
|
|
129
|
+
|
|
130
|
+
`--probe=https://app.example.com/status` previews two requests: HEAD and OPTIONS with a fixed probe Origin. Replace the example with an authorized target. Review the target, limits and privacy notice; repeat the same options with the displayed `--confirm-probe` digest to execute. The digest binds options, not domain ownership. This standalone CLI mode supports `--json`; it never reads project configuration or enables networking in `scan()` or the editor.
|
|
131
|
+
|
|
132
|
+
Only HTTPS on port 443 with a simple literal path is accepted: no credentials, queries, fragments or encoded path components. DNS answers must all be ordinary public addresses; connections are pinned while preserving hostname/TLS verification. No redirects or authenticated requests are made. Configured proxies, network debug settings and insecure TLS overrides are refused rather than bypassed. Limits: DNS 3 seconds, each request 5 seconds, response headers 16 KiB.
|
|
133
|
+
|
|
134
|
+
Optional `--probe-canary-sha256` adds GET only for a resource named `canship-canary`, `canship-canary.txt` or `canship-canary.json`. Use dedicated synthetic content of 16–4096 bytes. The response is hashed in memory; only match state and byte count are reported, never the body or computed digest. Compressed and oversized bodies fail. No Supabase/Firebase admin key or business-record export is supported.
|
|
135
|
+
|
|
136
|
+
Execution reveals the connecting IP and requested path to the target; requests can have side effects. Header presence, status codes and a readable canary do not prove general application security. Preview exits `0`; execution exits `0` for completed requests without review items, `2` for observations requiring review, or `3` for invalid/incomplete execution. JSON uses `probe-plan` or `probe-report`, not the scan-report schema. Live-target acceptance remains pending.
|
|
137
|
+
|
|
138
|
+
Address policy references: [IANA IPv4](https://www.iana.org/assignments/iana-ipv4-special-registry), [IANA IPv6](https://www.iana.org/assignments/iana-ipv6-special-registry), and [Azure platform address](https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16). Application filtering does not replace network egress controls.
|
|
139
|
+
|
|
140
|
+
## API and structured output
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
import { scan, summarize } from 'canship'
|
|
144
|
+
|
|
145
|
+
const result = await scan('./my-app', { noExcerpts: true })
|
|
146
|
+
console.log(summarize(result))
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`scan()` returns all confidence levels. Options: `only`, `skip`, `exclude`, `honorIgnoreMarkers` (default `true`), `noExcerpts` (default `false`), `signal`, and `onProgress`. It does not load configuration, apply baselines, write reports, or set process exit status. Invalid arguments throw. `listRules()` returns the rule catalogue; `getBuildInfo()` and `getCapabilities()` identify the build and permission boundaries.
|
|
150
|
+
|
|
151
|
+
`signal` accepts an AbortSignal. Cancellation rejects with `ScanCancelledError` (`name: "AbortError"`, `code: "SCAN_CANCELLED"`), not a clean or partial result. `onProgress` receives immutable stage/count snapshots; async callbacks are awaited and failures reject with `ScanProgressError`. Completion stages do not prove coverage; inspect the returned result. Cancellation is checked between file batches and rules, not inside active synchronous file/Git calls. CLI Ctrl+C uses the same boundary; progress contains no filenames.
|
|
152
|
+
|
|
153
|
+
JSON uses [schemaVersion 1](../schemas/scan-report-v1.schema.json). Check `partial`, `errors`, `skipped`, and `filesScanned` independently of exit status. New reports include stable `errors[].code` values; older reports may omit them. CLI failures include `[CODE]` on stderr without corrupting JSON stdout. SARIF includes evidence locations and execution diagnostics.
|
|
154
|
+
|
|
155
|
+
Canship v3 fingerprints identify findings for its own baselines and report comparisons. Within `partialFingerprints`, [GitHub code scanning](https://docs.github.com/en/code-security/reference/code-scanning/sarif-files/sarif-support#result-object) uses only `primaryLocationLineHash`, which the pinned `upload-sarif` Action adds from checked-out source and valid line locations; direct REST uploads cannot rely on Canship's custom v3 fingerprint for deduplication. That hash covers the flagged line and the code immediately after it, so editing nearby lines can close a GitHub alert and open a new one even when the Canship fingerprint is unchanged.
|
|
156
|
+
|
|
157
|
+
Build identity distinguishes development, prerelease, and release artifacts. Only a clean checkout matching the version tag is marked as a release; this label is not publisher authentication. JSON includes optional `build` metadata; consumers must tolerate absent metadata and unknown diagnostic codes. `--version` retains its package-version format.
|
|
158
|
+
|
|
159
|
+
## Compatibility
|
|
160
|
+
|
|
161
|
+
Pin exact scanner versions in CI and consult the documentation for that release. Before 1.0, inspect release notes and regenerate reports when upgrading.
|
|
162
|
+
|
|
163
|
+
For the 1.0 contract, breaking changes to public CLI options, exit semantics or exported API types require a major release. Incompatible report or baseline formats require a format-version change and migration guidance. Package versions and data-format versions are separate: scan JSON is v1, new baselines are v4 (v2/v3 readable), stable fingerprints are v3, and SARIF is 2.1.0. Dispatch JSON by operation `kind` and `schemaVersion`; ordinary scan reports have no `kind`. Accept documented optional additions and unknown diagnostic codes, but reject unsupported format versions rather than interpreting them as clean results.
|
|
164
|
+
|
|
165
|
+
Rule additions and detection corrections can change findings without breaking an interface. Review result and baseline changes after upgrades. Rule IDs and source fingerprints identify findings; wording and line moves do not. HTML structure, embedded view data, terminal spacing and internal modules are not machine APIs. Use exported APIs and documented JSON instead. The editor preview has its own version; the Action commit and npm scanner version are selected independently.
|
|
166
|
+
|
|
167
|
+
For support, start with `--doctor --json` and review the output locally. It omits source, environment-variable values, baseline entries and remote addresses; it is not a project archive and is never uploaded automatically.
|
|
168
|
+
|
|
169
|
+
## Privacy and limits
|
|
170
|
+
|
|
171
|
+
- Static checks may miss issues or flag intentional configurations. Business authorisation, rate limiting, dependency vulnerabilities, and deployed settings are not verified.
|
|
172
|
+
- Redaction covers recognised formats only. Unknown secrets may remain in excerpts; `--no-excerpts` omits excerpts. Paths, names, and baseline descriptions remain visible.
|
|
173
|
+
- Google/Firebase/Maps `AIza…` keys are treated as public identifiers, not leak evidence alone. Supabase checks use local migrations and supported bucket configuration.
|
|
174
|
+
- Evaluation downloads, optional SARIF uploads, and explicitly confirmed deployment probes may use the network. Static scanning stays offline.
|
|
175
|
+
- Symbolic links are not followed; nested repositories and submodules need separate scans. In-scope skipped paths and analysis limits mark coverage incomplete; auth helper resolution limits are noted on the affected finding instead, because they cannot hide findings. Dependency and build directories excluded by default do not count as coverage gaps.
|
|
176
|
+
|
|
177
|
+
| Limit | Bound |
|
|
178
|
+
|---|---|
|
|
179
|
+
| File reads | 2 MiB per file; 128 MiB and 10,000 files per scan, including file-type probes |
|
|
180
|
+
| Directory discovery | 50,000 entries; 16 levels |
|
|
181
|
+
| OpenAPI batches | 256 items per call, including spreads; 8 array levels |
|
|
182
|
+
| Project router factories | 8 resolution steps; 4,000 expression characters; 256 candidates per file; 8 literal base-path calls |
|
|
183
|
+
| Factory module metadata | 65,536 UTF-16 code units per config; 128 path mappings; 64 workspace patterns; 8 export-condition levels, 32 entries per level |
|
|
184
|
+
| Route registration context | 512 regions per file; 32 statement levels; 4,000-character prefixes; 4,096 project graph sites; 256 inherited middleware references per site |
|
|
185
|
+
| Findings | 100 per file, prioritising severity and confidence |
|
|
186
|
+
| Git history | 100 relevant revisions per file; 30 seconds per command |
|
|
187
|
+
| Auth helper resolution | 8 hops; 64 symbols per helper, 1,024 per route file |
|
|
188
|
+
| Delegated writes | 2 call levels; 256 callees per file; writes beyond these limits are not reported |
|
|
189
|
+
| Identity/control flow | 8 value hops; 4,000 expression characters; 512 assignments/regions per function; 8 nested regions |
|
|
190
|
+
| Request-input tracking | 8 value hops; 512 assignments/regions; 64 KiB per expression; 8 URL-analysis levels; 200 static-prefix characters |
|
|
191
|
+
| Supabase policy/bucket parsing | 4,000 characters per statement |
|
|
192
|
+
|
|
193
|
+
Evidence traces are capped at 24 steps and disclose truncation.
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
Publishing is staged for human approval: stable versions use `latest`; prereleases use `next`. The workflow requires an exact SemVer version without build metadata and a matching Git tag. Pushing `main` does not publish. See [npm staged publishing](https://docs.npmjs.com/cli/v11/commands/npm-stage/).
|
|
198
|
+
|
|
199
|
+
From the repository root, after installing development dependencies, `node --import tsx scripts/prepare-sarif-validation.ts` previews five synthetic SARIF cases: initial findings, a repeat, moved lines, changed wording/version, and fewer findings. It does not scan, write files or upload. To verify GitHub alert continuity and closure, upload the reports with explicit approval and matching synthetic fixture commits on an isolated test branch.
|
|
200
|
+
|
|
201
|
+
The repository includes a [synthetic HTML demonstration](https://github.com/Tasomei/canship/blob/main/docs/demo.html). Download and open it locally; it is not included in the npm package and never scans a project. The page and copied prompts identify the data as examples. `npm run demo` previews HTML on stdout, `npm run demo -- --check` verifies the committed artifact, and explicit `npm run demo -- --write` regenerates it. Automated tests reject a stale demo.
|
|
202
|
+
|
|
203
|
+
The [VS Code extension](https://github.com/Tasomei/canship/tree/main/extensions/vscode#readme) is a development preview, separate from the npm package. See its README for host acceptance coverage and remaining checks. Marketplace publication is pending.
|
|
204
|
+
|
|
205
|
+
```powershell
|
|
206
|
+
npm ci
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```powershell
|
|
210
|
+
npm run prepublishOnly
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```powershell
|
|
214
|
+
npm run test:package
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```powershell
|
|
218
|
+
npm run evaluate
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
New rules require positive and negative [fixtures](https://github.com/Tasomei/canship/tree/main/test/fixtures/). Pinned project evaluation: [manifest](https://github.com/Tasomei/canship/tree/main/test/evaluation/projects.json), [fetch script](https://github.com/Tasomei/canship/blob/main/scripts/fetch-evaluation-projects.mjs), [evaluator](https://github.com/Tasomei/canship/blob/main/scripts/evaluate-projects.ts). Passing samples do not establish real-world detection rates.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canship",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Pre-deployment security checks for JS/TS web apps: offline static scanning for credentials, access rules and unsafe request input, with opt-in deployment probes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
7
7
|
"audit",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"./package.json": "./package.json",
|
|
39
39
|
"./schemas/scan-report-v1.schema.json": "./schemas/scan-report-v1.schema.json",
|
|
40
|
+
"./schemas/config-v1.schema.json": "./schemas/config-v1.schema.json",
|
|
40
41
|
"./dist/cli.js": "./dist/cli.js"
|
|
41
42
|
},
|
|
42
43
|
"bin": {
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
"schemas",
|
|
48
49
|
"README.md",
|
|
49
50
|
"README-zh-CN.md",
|
|
51
|
+
"docs/reference.md",
|
|
52
|
+
"docs/reference-zh-CN.md",
|
|
53
|
+
"docs/framework-support.md",
|
|
54
|
+
"docs/framework-support-zh-CN.md",
|
|
50
55
|
"LICENSE"
|
|
51
56
|
],
|
|
52
57
|
"engines": {
|
|
@@ -54,6 +59,11 @@
|
|
|
54
59
|
},
|
|
55
60
|
"scripts": {
|
|
56
61
|
"build": "tsup",
|
|
62
|
+
"build:extension": "npm run build && node scripts/build-extension.mjs",
|
|
63
|
+
"package:extension": "npm run build:extension && npm --prefix tools/vsix ci --ignore-scripts --no-audit --no-fund && node scripts/package-extension.mjs",
|
|
64
|
+
"test:extension-package": "npm run build:extension && npm --prefix tools/vsix ci --ignore-scripts --no-audit --no-fund && node --test tools/vsix/test/package.test.mjs",
|
|
65
|
+
"benchmark": "npm run build && node --max-old-space-size=256 scripts/benchmark.mjs",
|
|
66
|
+
"demo": "tsx scripts/render-demo.ts",
|
|
57
67
|
"dev": "tsup --watch",
|
|
58
68
|
"typecheck": "tsc --noEmit",
|
|
59
69
|
"test": "node scripts/run-tests.mjs",
|
|
@@ -67,6 +77,7 @@
|
|
|
67
77
|
},
|
|
68
78
|
"devDependencies": {
|
|
69
79
|
"@types/node": "^24.0.0",
|
|
80
|
+
"@types/vscode": "1.95.0",
|
|
70
81
|
"ajv": "^8.20.0",
|
|
71
82
|
"tsup": "^8.3.5",
|
|
72
83
|
"tsx": "^4.19.2",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/Tasomei/canship/main/schemas/config-v1.schema.json",
|
|
4
|
+
"title": "Canship configuration",
|
|
5
|
+
"description": "Project settings for the local static scanner. CLI flags override these settings.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"exclude": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"maxItems": 64,
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"minLength": 1,
|
|
15
|
+
"maxLength": 512,
|
|
16
|
+
"pattern": "^(?![!])(?!.*[:*?\\[\\]{}\\u0000-\\u001f\\u007f-\\u009f\\u2028\\u2029])(?!(?:.*[/\\\\])?\\.{1,2}(?:[/\\\\]|$))[^\\s/\\\\](?:[^/\\\\]*[^\\s/\\\\])?(?:[/\\\\][^\\s/\\\\](?:[^/\\\\]*[^\\s/\\\\])?)*[/\\\\]?$"
|
|
17
|
+
},
|
|
18
|
+
"description": "Literal project-relative files or directories, case-sensitive; no globs. Exclusions affect source and environment-history checks, not configuration or metadata loading."
|
|
19
|
+
},
|
|
20
|
+
"$schema": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"description": "Editor schema reference. Canship never fetches or executes this value."
|
|
24
|
+
},
|
|
25
|
+
"baseline": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"description": "Existing baseline path inside the scan directory. Findings are accepted, not fixed; runtime checks enforce path containment and validate the file."
|
|
29
|
+
},
|
|
30
|
+
"only": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": {
|
|
33
|
+
"$ref": "#/definitions/selector"
|
|
34
|
+
},
|
|
35
|
+
"description": "Run matching rule IDs or namespaces. Cannot be combined with skip."
|
|
36
|
+
},
|
|
37
|
+
"skip": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": {
|
|
40
|
+
"$ref": "#/definitions/selector"
|
|
41
|
+
},
|
|
42
|
+
"description": "Exclude matching rule IDs or namespaces. Cannot be combined with only."
|
|
43
|
+
},
|
|
44
|
+
"all": {
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"default": false,
|
|
47
|
+
"description": "Show likely findings. Hidden findings still affect exit status."
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"not": {
|
|
51
|
+
"required": [
|
|
52
|
+
"only",
|
|
53
|
+
"skip"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"definitions": {
|
|
57
|
+
"selector": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"enum": [
|
|
60
|
+
"api",
|
|
61
|
+
"api/admin-db-access-without-auth",
|
|
62
|
+
"api/db-write-without-auth",
|
|
63
|
+
"auth",
|
|
64
|
+
"auth/unverified-session",
|
|
65
|
+
"cors",
|
|
66
|
+
"cors/reflected-origin-with-credentials",
|
|
67
|
+
"cors/wildcard-with-credentials",
|
|
68
|
+
"exposure",
|
|
69
|
+
"exposure/private-name-in-public-env",
|
|
70
|
+
"exposure/secret-in-public-env",
|
|
71
|
+
"exposure/supabase-service-role-in-client",
|
|
72
|
+
"firebase",
|
|
73
|
+
"firebase/open-rules",
|
|
74
|
+
"firebase/test-mode-rules",
|
|
75
|
+
"gitleak",
|
|
76
|
+
"gitleak/env-in-history",
|
|
77
|
+
"gitleak/env-tracked",
|
|
78
|
+
"injection",
|
|
79
|
+
"injection/command",
|
|
80
|
+
"injection/sql",
|
|
81
|
+
"redirect",
|
|
82
|
+
"redirect/open",
|
|
83
|
+
"secrets",
|
|
84
|
+
"secrets/hardcoded",
|
|
85
|
+
"secrets/hardcoded/anthropic",
|
|
86
|
+
"secrets/hardcoded/aws-access-key-id",
|
|
87
|
+
"secrets/hardcoded/db-connection-string",
|
|
88
|
+
"secrets/hardcoded/github-token",
|
|
89
|
+
"secrets/hardcoded/google-api-key",
|
|
90
|
+
"secrets/hardcoded/groq",
|
|
91
|
+
"secrets/hardcoded/huggingface",
|
|
92
|
+
"secrets/hardcoded/npm-token",
|
|
93
|
+
"secrets/hardcoded/openai",
|
|
94
|
+
"secrets/hardcoded/openrouter",
|
|
95
|
+
"secrets/hardcoded/perplexity",
|
|
96
|
+
"secrets/hardcoded/private-key",
|
|
97
|
+
"secrets/hardcoded/replicate",
|
|
98
|
+
"secrets/hardcoded/sendgrid",
|
|
99
|
+
"secrets/hardcoded/slack-token",
|
|
100
|
+
"secrets/hardcoded/stripe-live",
|
|
101
|
+
"secrets/hardcoded/supabase-secret-key",
|
|
102
|
+
"secrets/hardcoded/xai",
|
|
103
|
+
"ssrf",
|
|
104
|
+
"ssrf/request-url",
|
|
105
|
+
"supabase",
|
|
106
|
+
"supabase/permissive-policy",
|
|
107
|
+
"supabase/public-bucket-listing",
|
|
108
|
+
"supabase/rls-not-enabled",
|
|
109
|
+
"webhook",
|
|
110
|
+
"webhook/unverified-signature"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
"type": "object",
|
|
5
5
|
"required": ["schemaVersion", "version", "root", "filesScanned", "durationMs", "partial", "errors", "skipped", "ignored", "ignoredFindings", "ruleSelection", "vendored", "hiddenLikely", "baselineSuppressed", "baselineStale", "findings"],
|
|
6
6
|
"properties": {
|
|
7
|
+
"build": {
|
|
8
|
+
"type":"object", "required":["version","channel","revision","dirty"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"version":{"type":"string"},
|
|
11
|
+
"channel":{"enum":["development","prerelease","release"]},
|
|
12
|
+
"revision":{"anyOf":[{"type":"null"},{"type":"string","pattern":"^(?:[a-f0-9]{40}|[a-f0-9]{64})$"}]},
|
|
13
|
+
"dirty":{"type":["boolean","null"]}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
7
16
|
"schemaVersion": { "const": 1 },
|
|
8
17
|
"version": { "type": "string", "minLength": 1 },
|
|
9
18
|
"root": { "type": "string" },
|
|
@@ -14,6 +23,7 @@
|
|
|
14
23
|
"hiddenLikely": { "$ref": "#/definitions/count" },
|
|
15
24
|
"baselineSuppressed": { "$ref": "#/definitions/count" },
|
|
16
25
|
"baselineStale": { "$ref": "#/definitions/count" },
|
|
26
|
+
"baselineExpired": { "$ref": "#/definitions/count" },
|
|
17
27
|
"excerptsOmitted": { "type": "boolean" },
|
|
18
28
|
"changeView": {
|
|
19
29
|
"type": "object",
|
|
@@ -29,6 +39,13 @@
|
|
|
29
39
|
}
|
|
30
40
|
},
|
|
31
41
|
"ignored": { "$ref": "#/definitions/strings" },
|
|
42
|
+
"exclusions": {
|
|
43
|
+
"type": "object", "required": ["requested", "matched"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"requested": { "$ref": "#/definitions/strings" },
|
|
46
|
+
"matched": { "$ref": "#/definitions/strings" }
|
|
47
|
+
}
|
|
48
|
+
},
|
|
32
49
|
"errors": {
|
|
33
50
|
"type": "array",
|
|
34
51
|
"items": {
|
|
@@ -38,7 +55,8 @@
|
|
|
38
55
|
"ruleId": { "type": "string" },
|
|
39
56
|
"file": { "type": ["string", "null"] },
|
|
40
57
|
"message": { "type": "string" },
|
|
41
|
-
"kind": { "enum": ["crashed", "incomplete"] }
|
|
58
|
+
"kind": { "enum": ["crashed", "incomplete"] },
|
|
59
|
+
"code": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" }
|
|
42
60
|
}
|
|
43
61
|
}
|
|
44
62
|
},
|