fallow 2.88.3 → 2.90.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/package.json +9 -9
- package/schema.json +11 -3
- package/skills/fallow/SKILL.md +5 -3
- package/skills/fallow/references/cli-reference.md +90 -18
- package/types/output-contract.d.ts +515 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.90.0",
|
|
4
4
|
"description": "Deterministic codebase intelligence for TypeScript and JavaScript. Quality, risk, architecture, dependencies, duplication, and safe cleanup evidence for humans, CI, and agents. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, 96 framework plugins.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -83,13 +83,13 @@
|
|
|
83
83
|
"@tanstack/intent": "0.0.41"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@fallow-cli/darwin-arm64": "2.
|
|
87
|
-
"@fallow-cli/darwin-x64": "2.
|
|
88
|
-
"@fallow-cli/linux-x64-gnu": "2.
|
|
89
|
-
"@fallow-cli/linux-arm64-gnu": "2.
|
|
90
|
-
"@fallow-cli/linux-x64-musl": "2.
|
|
91
|
-
"@fallow-cli/linux-arm64-musl": "2.
|
|
92
|
-
"@fallow-cli/win32-arm64-msvc": "2.
|
|
93
|
-
"@fallow-cli/win32-x64-msvc": "2.
|
|
86
|
+
"@fallow-cli/darwin-arm64": "2.90.0",
|
|
87
|
+
"@fallow-cli/darwin-x64": "2.90.0",
|
|
88
|
+
"@fallow-cli/linux-x64-gnu": "2.90.0",
|
|
89
|
+
"@fallow-cli/linux-arm64-gnu": "2.90.0",
|
|
90
|
+
"@fallow-cli/linux-x64-musl": "2.90.0",
|
|
91
|
+
"@fallow-cli/linux-arm64-musl": "2.90.0",
|
|
92
|
+
"@fallow-cli/win32-arm64-msvc": "2.90.0",
|
|
93
|
+
"@fallow-cli/win32-x64-msvc": "2.90.0"
|
|
94
94
|
}
|
|
95
95
|
}
|
package/schema.json
CHANGED
|
@@ -1188,11 +1188,11 @@
|
|
|
1188
1188
|
]
|
|
1189
1189
|
},
|
|
1190
1190
|
"SecurityConfig": {
|
|
1191
|
-
"description": "Scopes the
|
|
1191
|
+
"description": "Scopes the security categories used by `fallow security`. An absent block\nadmits every catalogue category. `hardcoded-secret` is include-required and\nonly runs when explicitly listed in `security.categories.include`.",
|
|
1192
1192
|
"type": "object",
|
|
1193
1193
|
"properties": {
|
|
1194
1194
|
"categories": {
|
|
1195
|
-
"description": "Include/exclude filter over
|
|
1195
|
+
"description": "Include/exclude filter over category ids (e.g. `dangerous-html`).",
|
|
1196
1196
|
"anyOf": [
|
|
1197
1197
|
{
|
|
1198
1198
|
"$ref": "#/$defs/SecurityCategories"
|
|
@@ -1206,7 +1206,7 @@
|
|
|
1206
1206
|
"additionalProperties": false
|
|
1207
1207
|
},
|
|
1208
1208
|
"SecurityCategories": {
|
|
1209
|
-
"description": "Include/exclude lists scoping the active security
|
|
1209
|
+
"description": "Include/exclude lists scoping the active security categories. When `include`\nis set, only those categories are active; `exclude` removes categories from\nthe admitted set. Both unset admits catalogue categories. `hardcoded-secret`\nstill requires explicit inclusion.",
|
|
1210
1210
|
"type": "object",
|
|
1211
1211
|
"properties": {
|
|
1212
1212
|
"include": {
|
|
@@ -1762,7 +1762,15 @@
|
|
|
1762
1762
|
"CacheConfig": {
|
|
1763
1763
|
"type": "object",
|
|
1764
1764
|
"properties": {
|
|
1765
|
+
"dir": {
|
|
1766
|
+
"description": "Directory for fallow's persistent analysis cache. Relative paths resolve\nfrom the project root.",
|
|
1767
|
+
"type": [
|
|
1768
|
+
"string",
|
|
1769
|
+
"null"
|
|
1770
|
+
]
|
|
1771
|
+
},
|
|
1765
1772
|
"maxSizeMb": {
|
|
1773
|
+
"description": "Maximum size of the persistent extraction cache, in megabytes.",
|
|
1766
1774
|
"type": [
|
|
1767
1775
|
"integer",
|
|
1768
1776
|
"null"
|
package/skills/fallow/SKILL.md
CHANGED
|
@@ -75,7 +75,7 @@ cargo install fallow-cli # build from source
|
|
|
75
75
|
| `health` | Function complexity analysis (also covers Angular templates as synthetic `<template>` findings: external `.html` files via `templateUrl` AND inline `@Component({ template: \`...\` })` literals; suppress external with `<!-- fallow-ignore-file complexity -->` at the top of the `.html` file, suppress inline with `// fallow-ignore-next-line complexity` directly above the `@Component` decorator) | `--complexity`, `--max-cyclomatic`, `--max-cognitive`, `--max-crap`, `--top`, `--sort`, `--file-scores`, `--hotspots`, `--ownership`, `--ownership-emails`, `--targets`, `--effort`, `--score`, `--min-score`, `--since`, `--min-commits`, `--save-snapshot`, `--trend`, `--coverage-gaps`, `--coverage`, `--coverage-root`, `--runtime-coverage`, `--min-invocations-hot`, `--min-observation-volume`, `--low-traffic-threshold`, `--workspace`, `--changed-workspaces`, `--baseline`, `--save-baseline` |
|
|
76
76
|
| `audit` | Combined dead-code + complexity + duplication for changed files | `--base`, `--gate`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--explain`, `--explain-skipped`, `--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`, `--max-crap`, `--coverage`, `--coverage-root`, `--include-entry-exports` |
|
|
77
77
|
| `flags` | Detect feature flag patterns (env vars, SDK calls, config objects) | `--top` |
|
|
78
|
-
| `security` | Surface opt-in local security candidates for agent verification (not confirmed vulnerabilities).
|
|
78
|
+
| `security` | Surface opt-in local security candidates for agent verification (not confirmed vulnerabilities). Rule families include the graph rule `client-server-leak`, a data-driven `tainted-sink` catalogue, and the include-required `hardcoded-secret` category for provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers. Most catalogue rows require non-literal input; narrowly literal-aware rows flag deterministic unsafe literals. Rules default off; suppress a file with `// fallow-ignore-file security-sink`; scope categories with `security.categories`. `hardcoded-secret` runs only when listed in `security.categories.include`. | `--format human|json|sarif`, `--changed-since`, `--file`, `--diff-file`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--sarif-file`, `--summary` |
|
|
79
79
|
| `explain` | Explain one issue type without running analysis | `<issue-type>`, `--format json` |
|
|
80
80
|
| `license` | Manage the local license JWT for continuous/cloud runtime monitoring (activate, status, refresh, deactivate) | `activate --trial --email <addr>`, `activate --from-file`, `activate --stdin`, `status`, `refresh`, `deactivate` |
|
|
81
81
|
| `telemetry` | Manage opt-in, off-by-default product telemetry (never collects code, paths, or names). Agents must not enable it; only the user may | `status`, `enable`, `disable`, `inspect --example` |
|
|
@@ -118,11 +118,11 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
|
|
|
118
118
|
|------|-------------|
|
|
119
119
|
| `analyze` | Full dead code analysis (unused files/exports/types/dependencies/members + circular dependencies + re-export cycles (barrel files that form a structural loop, silently breaking re-exports) + boundary violations + stale suppressions). Private type leaks are an opt-in API hygiene check via `issue_types: ["private-type-leaks"]`. Set `boundary_violations: true` as a convenience alias for `issue_types: ["boundary-violations"]`. Set `group_by` to `"owner"`, `"directory"`, `"package"`, or `"section"` to partition results. The `section` mode reads GitLab CODEOWNERS `[Section]` headers and emits `owners` metadata per group |
|
|
120
120
|
| `check_changed` | Incremental analysis of files changed since a git ref |
|
|
121
|
-
| `security_candidates` | Unverified local security candidates, not confirmed vulnerabilities (`fallow security --format json`). Read `security_findings[]` for category, CWE, evidence, and
|
|
121
|
+
| `security_candidates` | Unverified local security candidates, not confirmed vulnerabilities (`fallow security --format json`). Read `security_findings[]` for category, CWE, evidence, trace, optional `reachability`, and blind-spot counters. Each finding also carries an agent-actionable `candidate` (`source_kind`/`sink`/`boundary`), an optional `taint_flow` source-to-sink triple, and a stable `finding_id` (equal to the SARIF fingerprint) for cross-run correlation; there is no `impact` field (deciding exploitability is the agent's job). `reachability.untrusted_source_trace` is module-level import context only and does not prove value flow. Verify trace, reachability context, and evidence before editing code. Supports `root`, `config`, `workspace`, `paths`, `changed_since`, `changed_workspaces`, `no_cache`, and `threads`; `paths` forwards repeated `fallow security --file` filters for finding anchors, trace hops, or untrusted-source reachability trace hops. Inherits `FALLOW_DIFF_FILE` from the server environment for line-level diff scoping; raise `FALLOW_TIMEOUT_SECS` for large repos. |
|
|
122
122
|
| `find_dupes` | Code duplication detection. Set `changed_since` to scope to changed files since a git ref. Set `min_occurrences` (≥ 2, default 2) to hide pair-only clones and focus on widespread copy-paste; JSON gains `stats.clone_groups_below_min_occurrences` when the filter hides anything. Each `clone_groups[]` entry carries a stable `fingerprint`, usually `dup:<8hex>` and widened only on rare report collisions; pass it to `trace_clone` to deep-dive that group |
|
|
123
123
|
| `fix_preview` | Dry-run auto-fix preview |
|
|
124
124
|
| `fix_apply` | Apply auto-fixes (destructive) |
|
|
125
|
-
| `check_health` | Complexity metrics, health scores, hotspots, and refactoring targets. Optional `runtime_coverage` merges a V8 or Istanbul dump; tune it with `min_invocations_hot` (default 100), `min_observation_volume` (default 5000), and `low_traffic_threshold` (default 0.001). When runtime evidence combines with static usage, test coverage, CRAP/complexity, ownership, or change scope, read `coverage_intelligence` for stable `fallow:coverage-intel:<hash>` recommendations. Set `group_by` to `owner`, `directory`, `package`, or `section` for per-group `vital_signs` / `health_score`; SARIF results gain `properties.group`, CodeClimate issues gain a top-level `group` field |
|
|
125
|
+
| `check_health` | Complexity metrics, health scores, hotspots, and refactoring targets. Set `complexity_breakdown: true` to add a per-decision-point `contributions[]` array to each complexity finding (each `else-if`, nested `if`, boolean operator, loop, `case`, etc. with its source line and cyclomatic/cognitive weight) so you can explain WHY a function scored high and pinpoint refactor targets. Optional `runtime_coverage` merges a V8 or Istanbul dump; tune it with `min_invocations_hot` (default 100), `min_observation_volume` (default 5000), and `low_traffic_threshold` (default 0.001). When runtime evidence combines with static usage, test coverage, CRAP/complexity, ownership, or change scope, read `coverage_intelligence` for stable `fallow:coverage-intel:<hash>` recommendations. Set `group_by` to `owner`, `directory`, `package`, or `section` for per-group `vital_signs` / `health_score`; SARIF results gain `properties.group`, CodeClimate issues gain a top-level `group` field |
|
|
126
126
|
| `check_runtime_coverage` | Merge V8 or Istanbul runtime-coverage data into the health report. One local capture is free; continuous/cloud or multi-capture runtime monitoring is paid. Required `coverage` param (V8 dir, V8 JSON, or Istanbul `coverage-final.json`). Tuning knobs: `min_invocations_hot` (default 100), `min_observation_volume` (default 5000), `low_traffic_threshold` (default 0.001), `max_crap` (default 30.0), `top`, `group_by`. Cloud runtime rows can expose `resolutionStatus` / `mappingQuality` on function-list JSON and `resolution_status` / `mapping_quality` in runtime-context JSON. Use `coverage_intelligence` and the confidence table below before acting on file-level runtime signals. Long dumps may exceed the 120s MCP timeout; raise `FALLOW_TIMEOUT_SECS`. Pick this over `check_health` when you have a coverage dump. |
|
|
127
127
|
| `get_hot_paths` | Runtime-context slice over the same runtime coverage pipeline. Same params as `check_runtime_coverage`; read `runtime_coverage.hot_paths` for production hot paths. |
|
|
128
128
|
| `get_blast_radius` | Runtime-context slice for blast-radius review. Same params as `check_runtime_coverage`; read `runtime_coverage.blast_radius` for stable `fallow:blast:<hash>` IDs, caller counts, traffic-weighted caller reach, optional cloud deploy touch counts, and low/medium/high risk bands. |
|
|
@@ -366,6 +366,8 @@ Config fields:
|
|
|
366
366
|
- `ignoreExportsUsedInFile`: knip-compatible; suppress unused-export findings when the exported symbol is referenced inside the file that declares it. Boolean (`true` covers all kinds) or `{ "type": true, "interface": true }` object form for knip parity. Fallow groups type aliases and interfaces under the same `unused-types` issue, so both type-kind fields behave identically. References inside the export specifier itself (`export { foo }`, `export default foo`) do not count as same-file uses; those exports are still reported when no other in-file expression references the binding
|
|
367
367
|
- `publicPackages`: workspace packages that are public libraries; exported API surface from these packages is not flagged as unused
|
|
368
368
|
- `dynamicallyLoaded`: glob patterns for files loaded at runtime (plugin dirs, locale files); treated as always-used
|
|
369
|
+
- `cache.dir`: override the persistent extraction cache directory. `FALLOW_CACHE_DIR` wins over this config field, and `--no-cache` disables caching entirely
|
|
370
|
+
- `cache.maxSizeMb`: cap the serialized extraction cache size in megabytes. `FALLOW_CACHE_MAX_SIZE` wins over this config field
|
|
369
371
|
- `usedClassMembers`: class method/property names that extend the built-in Angular/React lifecycle allowlist with framework-invoked names. Each entry is a plain string (global suppression) or a scoped object `{ extends?, implements?, members }` matching only classes with the given heritage. Strings can be exact names (`"agInit"`) or glob patterns (`"*"` matches every member, `"enter*"` prefix, `"*Handler"` suffix, `"on*Event"` combined). Use scoped rules for common names like `refresh` or `execute` to avoid false negatives on unrelated classes; global strings for unique names like `agInit`. Example: `["agInit", { "implements": "ICellRendererAngularComp", "members": ["refresh"] }, { "extends": "BaseCommand", "members": ["execute"] }, { "extends": "GrammarBaseListener", "members": ["enter*", "exit*"] }]`. Glob patterns that match zero members emit a `WARN` so dead allowlist entries surface. An unconstrained scoped rule (no `extends` or `implements`) is rejected at load time. Use plugin-level `usedClassMembers` in a `.fallow/plugins/*.jsonc` file for library-specific allowlists
|
|
370
372
|
- `resolve.conditions`: additional package.json `exports` / `imports` condition names to honor during module resolution. Baseline conditions (`development`, `import`, `require`, `default`, `types`, `node`, plus `react-native` / `browser` under RN/Expo) are always included; user entries prepend ahead of them. Use for community conditions like `worker`, `edge-light`, `deno`, or custom bundler conditions. Example: `{ "resolve": { "conditions": ["worker", "edge-light"] } }`
|
|
371
373
|
|
|
@@ -40,9 +40,11 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
|
|
|
40
40
|
|------|------|---------|-------------|
|
|
41
41
|
| `--format` | `human\|json\|sarif\|compact\|markdown\|codeclimate\|gitlab-codequality\|pr-comment-github\|pr-comment-gitlab\|review-github\|review-gitlab` | `human` | Output format |
|
|
42
42
|
| `--quiet` | bool | `false` | Suppress progress bars and timing on stderr |
|
|
43
|
+
| `-o, --output-file` | path | (none) | Write the report to a file instead of stdout, for any `--format` (no ANSI codes). Progress and a confirmation stay on stderr (suppressed by `--quiet`). Valid with dead-code/dupes/health/security/bare; composes with `--sarif-file`. |
|
|
43
44
|
| `--legacy-envelope` | bool | `false` | Remove the top-level `kind` field from typed JSON roots for one migration cycle |
|
|
44
45
|
| `--changed-since` | string | — | Only analyze files changed since a git ref (e.g., `main`, `HEAD~3`) |
|
|
45
46
|
| `--production` | bool | `false` | Exclude test/dev files, only start/build scripts (applies to every analysis) |
|
|
47
|
+
| `--no-production` | bool | `false` | Force production mode off, overriding a project config's `production: true` (applies to every analysis; conflicts with `--production`) |
|
|
46
48
|
| `--production-dead-code` | bool | `false` | Per-analysis production mode for dead-code. Bare combined runs and `fallow audit` only. |
|
|
47
49
|
| `--production-health` | bool | `false` | Per-analysis production mode for health. Bare combined runs and `fallow audit` only. |
|
|
48
50
|
| `--production-dupes` | bool | `false` | Per-analysis production mode for duplication. Bare combined runs and `fallow audit` only. |
|
|
@@ -51,7 +53,14 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
|
|
|
51
53
|
| `--workspace` | string | — | Scope to one or more workspaces. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation (`!apps/legacy`) supported. Matched against package name AND workspace path relative to repo root. |
|
|
52
54
|
| `--changed-workspaces` | string (git ref) | — | Git-derived monorepo CI scoping: scope to workspaces containing any file changed since `REF` (e.g. `origin/main`). Auto-derives the workspace set from `git diff`. Mutually exclusive with `--workspace`. Missing ref is a hard error (exit 2), not silent full-scope fallback. |
|
|
53
55
|
| `--include-dupes` | bool | `false` | Cross-reference with duplication findings |
|
|
56
|
+
| `--dupes-mode` | enum | `config` | Override duplicate detection mode in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --mode`. |
|
|
57
|
+
| `--dupes-threshold` | number | `config` | Override the duplication percentage failure threshold in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --threshold`. |
|
|
58
|
+
| `--dupes-min-tokens` | number | `config` | Override the minimum token count for clone detection in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --min-tokens`. |
|
|
59
|
+
| `--dupes-min-lines` | number | `config` | Override the minimum line count for clone detection in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --min-lines`. |
|
|
54
60
|
| `--dupes-min-occurrences` | number | `config` | Override the minimum clone occurrences in combined mode (must be >= 2). Falls back to the config value when unset. Mirrors the standalone `dupes --min-occurrences`. |
|
|
61
|
+
| `--dupes-skip-local` | bool | `config` | Only report cross-directory duplicates in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --skip-local`. |
|
|
62
|
+
| `--dupes-cross-language` | bool | `config` | Enable TypeScript to JavaScript duplicate matching in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --cross-language`. |
|
|
63
|
+
| `--dupes-ignore-imports` | bool | `config` | Exclude import declarations from duplicate detection in combined mode. Falls back to the config value when unset. Mirrors the standalone `dupes --ignore-imports`. |
|
|
55
64
|
| `--file` | path (multiple) | — | Scope output to specific files. Only issues in the specified files are reported. Project-wide dependency issues are suppressed. Warns on non-existent paths. Useful for lint-staged |
|
|
56
65
|
| `--include-entry-exports` | bool | `false` | Report unused exports in entry files (package.json `main`/`exports`, framework pages). Catches typos like `meatdata` vs `metadata`. Global flag, also accepted on combined mode (`fallow --include-entry-exports`) and `fallow audit`. Also configurable as `includeEntryExports: true` in fallow config |
|
|
57
66
|
| `--trace` | `FILE:EXPORT` | — | Trace export usage chain |
|
|
@@ -227,7 +236,7 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
|
|
|
227
236
|
|
|
228
237
|
### On-disk drift protection
|
|
229
238
|
|
|
230
|
-
`fallow fix` captures every parsed source file's xxh3 content hash during the in-process analysis and recomputes it at fix time. Files whose hash drifted between analysis and write (parallel editor save, CI rebase, concurrent tool) are skipped with `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "content_changed"}` in the JSON output and `Skipping <path>: file content changed since fallow
|
|
239
|
+
`fallow fix` captures every parsed source file's xxh3 content hash during the in-process analysis and recomputes it at fix time. Files whose hash drifted between analysis and write (parallel editor save, CI rebase, concurrent tool) are skipped with `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "content_changed"}` in the JSON output and `Skipping <path>: file content changed since fallow dead-code ran. Re-run fallow fix to refresh the analysis first.` on stderr (gated on non-quiet). A run with any content-changed skip exits with code 2 so CI does not treat the partial run as a clean no-op. The JSON envelope's top-level `skipped_content_changed: number` is always present and disjoint from `skipped` (which still tallies catalog / YAML guard skips only). Per-file writes are batched: each rewrite is staged to a sibling temp file, and the orchestrator promotes the batch only after every stage succeeds. A stage failure leaves every target file at its original content. Hash precondition covers source files (TS, JS, Vue, Svelte, Astro, MDX); `package.json` and `pnpm-workspace.yaml` are not in the captured hash map because the extract layer does not parse them, but the dep and catalog fixers re-parse those files at fix time as the natural safety net.
|
|
231
240
|
|
|
232
241
|
### Low-confidence export removals
|
|
233
242
|
|
|
@@ -236,7 +245,7 @@ Issue #602: `fallow fix` withholds unused-export removals when the consumer may
|
|
|
236
245
|
- **Off-graph consumer directories.** The file is under any of `__mocks__`, `__fixtures__`, `fixtures`, `e2e`, `e2e-tests`, `cypress`, `playwright`, `examples`, `evals`, `golden` (matched on any path segment). Catches Vitest mock aliases, off-workspace e2e suites, and fixture / golden harnesses. Plain `test` / `tests` / `__tests__` are deliberately NOT on the list, so genuinely-dead test helpers still auto-remove.
|
|
237
246
|
- **Files with an unresolved import.** The file itself imports something fallow could not resolve, so its local usage graph is incomplete.
|
|
238
247
|
|
|
239
|
-
JSON output carries `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "low_confidence_off_graph"}` (or `"low_confidence_unresolved_imports"`) plus a top-level counter `skipped_low_confidence_exports: number` (always present), disjoint from `skipped`. Unlike the drift and encoding skips this is INTENTIONAL and does NOT change the exit code; the export stays reported by `fallow
|
|
248
|
+
JSON output carries `{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "low_confidence_off_graph"}` (or `"low_confidence_unresolved_imports"`) plus a top-level counter `skipped_low_confidence_exports: number` (always present), disjoint from `skipped`. Unlike the drift and encoding skips this is INTENTIONAL and does NOT change the exit code; the export stays reported by `fallow dead-code` for manual review. High-confidence exports in normal source files are removed unchanged. The AI agent should report kept exports to the user and let them decide whether the export is truly unused before removing it by hand.
|
|
240
249
|
|
|
241
250
|
### File encoding contract
|
|
242
251
|
|
|
@@ -287,7 +296,7 @@ fallow list --workspaces --format json --quiet
|
|
|
287
296
|
fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
|
|
288
297
|
```
|
|
289
298
|
|
|
290
|
-
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (`undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`) with a typed payload (`error`, `pattern`, or none). The same `workspace_diagnostics[]` array is also surfaced on `fallow
|
|
299
|
+
The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (`undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`) with a typed payload (`error`, `pattern`, or none). The same `workspace_diagnostics[]` array is also surfaced on `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes (omitted when empty). A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues.
|
|
291
300
|
|
|
292
301
|
The `--boundaries` JSON output carries `boundaries.logical_groups[]` alongside the existing `zones[]` / `rules[]` arrays. Each logical-group entry surfaces a user-authored `autoDiscover` parent zone (which expansion otherwise flattens into per-child zones like `features/auth` / `features/billing`): `name`, `children`, `auto_discover` (verbatim user strings), `status` (`ok` / `empty` / `invalid_path`), `source_zone_index`, summed `file_count`, optional `authored_rule` (the pre-expansion `{ allow, allowTypeOnly }` keyed on the parent), optional `fallback_zone` cross-reference when the parent also kept its own `patterns` (Bulletproof case), optional `merged_from` (parent zone indices when the user declared the same parent name twice; surfaces the duplicate in JSON instead of only in `tracing::warn!`), optional `original_zone_root` (echo of the parent's `root` subtree scope for monorepo patchers), and optional `child_source_indices` (parallel to `children`, attributing each child to a specific `auto_discover` entry when multiple paths were authored). The full shape is documented in `docs/output-schema.json` under `ListBoundariesOutput`.
|
|
293
302
|
|
|
@@ -368,6 +377,7 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
368
377
|
| `--top` | number | — | Only show the top N most complex functions (and file scores/hotspots/targets) |
|
|
369
378
|
| `--sort` | `cyclomatic\|cognitive\|lines\|severity` | `cyclomatic` | Sort order for complexity findings |
|
|
370
379
|
| `--complexity` | bool | `false` | Show only function complexity findings. When no section flags are set, all sections are shown by default. |
|
|
380
|
+
| `--complexity-breakdown` | bool | `false` | Add a per-decision-point `contributions[]` array to each complexity finding in `--format json`. Each entry names the construct (`if`, `else-if`, `ternary`, boolean operator, loop, `case`, `catch`, `optional-chain`, ...) and carries its source line, the metric it adds to (`cyclomatic` or `cognitive`), its weight, and the nesting depth, so a consumer can explain WHY a function scored high. Off by default (no change to existing JSON/SARIF/markdown). Used by the VS Code inline editor breakdown and the MCP `check_health` `complexity_breakdown` param. |
|
|
371
381
|
| `--file-scores` | bool | `false` | Show only per-file health scores (maintainability index, LOC, fan-in, fan-out, dead code ratio, complexity density, CRAP risk). Runs the full analysis pipeline. Sorted by risk-aware triage concern: lower maintainability index and higher CRAP risk first. When no section flags are set, all sections are shown by default. |
|
|
372
382
|
| `--hotspots` | bool | `false` | Show only hotspots: files that are both complex and frequently changing. Combines git churn history with complexity data. Requires a git repository. When no section flags are set, all sections are shown by default. |
|
|
373
383
|
| `--targets` | bool | `false` | Show only refactoring targets: ranked recommendations based on complexity, coupling, churn, and dead code signals. Categories: churn+complexity, circular dep, high impact, dead code, complexity, coupling. When no section flags are set, all sections are shown by default. |
|
|
@@ -376,8 +386,9 @@ Angular templates contribute synthetic `<template>` complexity findings whenever
|
|
|
376
386
|
| `--min-score` | number | — | Fail (exit 1) only when the health score is below this threshold. Implies `--score`. Authoritative CI quality gate: when set, complexity findings are demoted to informational and the exit code is driven solely by the score, so `--min-score 0` always exits 0. Composes with `--min-severity`. |
|
|
377
387
|
| `--min-severity` | `moderate\|high\|critical` | — | Only exit with an error for findings at or above this severity. Composes with `--min-score` (the run fails if either gate trips). |
|
|
378
388
|
| `--report-only` | bool | `false` | Print the score and findings but never fail CI (always exit 0). Advisory mode. Mutually exclusive with `--min-score` and `--min-severity`. |
|
|
379
|
-
| `--since` | string | `6m` | Git history window for hotspot analysis. Accepts durations (`6m`, `90d`, `1y`, `2w`) or ISO dates (`2025-06-01`). |
|
|
389
|
+
| `--since` | string | `6m` | Git history window for hotspot analysis. Accepts durations (`6m`, `90d`, `1y`, `2w`) or ISO dates (`2025-06-01`). Ignored when `--churn-file` is set. |
|
|
380
390
|
| `--min-commits` | number | `3` | Minimum number of commits for a file to be included in hotspot ranking. |
|
|
391
|
+
| `--churn-file` | string | (none) | Import change history from a `fallow-churn/v1` JSON file (`{schema, events:[{path, timestamp, author, added, deleted}]}`, one entry per changed file per commit) instead of `git log`, so `--hotspots`/`--ownership`/`--targets` work on non-git VCS (Yandex Arc, Mercurial, Perforce). Resolved relative to `--root`; wins over git. Authoritative for the window, so `--since` then only labels output. Malformed file exits 2; `audit`/`impact`/`--changed-since` still require git. |
|
|
381
392
|
| `--ownership` | bool | `false` | Attach ownership signals to hotspot entries: bus factor (Avelino truck factor), contributor count, top contributor with stale-days, recent contributors (top-3), `suggested_reviewers`, declared CODEOWNERS owner, `ownership_state`, ownership drift, unowned-hotspot detection. Human output gains a project-level summary line. JSON adds `low-bus-factor`, `unowned-hotspot`, `ownership-drift` action types. Test files get a `[test]` tag. Implies `--hotspots`. Requires git. |
|
|
382
393
|
| `--ownership-emails` | `raw\|handle\|anonymized\|hash` | `handle` | Privacy mode for author emails. `handle` shows the local-part only (default, with GitHub noreply unwrap and deterministic same-handle disambiguation). `anonymized` emits stable `xxh3:` pseudonyms; `hash` remains accepted as the legacy spelling. `raw` shows full addresses. Use `anonymized` in regulated environments. Implies `--ownership`. Configure default via `health.ownership.emailMode`. |
|
|
383
394
|
| `--changed-since` | string | — | Only analyze files changed since a git ref |
|
|
@@ -486,7 +497,7 @@ fallow health --format json --quiet --trend
|
|
|
486
497
|
{
|
|
487
498
|
"kind": "health",
|
|
488
499
|
"schema_version": 7,
|
|
489
|
-
"version": "2.
|
|
500
|
+
"version": "2.90.0",
|
|
490
501
|
"elapsed_ms": 32,
|
|
491
502
|
"summary": {
|
|
492
503
|
"files_analyzed": 482,
|
|
@@ -796,6 +807,7 @@ Audits changed files for dead code, complexity, and duplication. Returns a verdi
|
|
|
796
807
|
| `--base` | string | auto-detect | Git ref to compare against (alias for `--changed-since`) |
|
|
797
808
|
| `--gate` | `new-only\|all` | `new-only` | Which findings affect the verdict. `new-only` gates only introduced findings; `all` gates every finding in changed files and skips the extra base-snapshot attribution pass. |
|
|
798
809
|
| `--production` | bool | false | Exclude test/story/dev files (applies to dead-code, health, and dupes) |
|
|
810
|
+
| `--no-production` | bool | false | Force production mode off, overriding a project config's `production: true` (conflicts with `--production`) |
|
|
799
811
|
| `--production-dead-code` | bool | false | Per-analysis production mode for the dead-code sub-analysis only |
|
|
800
812
|
| `--production-health` | bool | false | Per-analysis production mode for the health sub-analysis only |
|
|
801
813
|
| `--production-dupes` | bool | false | Per-analysis production mode for the duplication sub-analysis only |
|
|
@@ -876,7 +888,7 @@ fallow audit \
|
|
|
876
888
|
{
|
|
877
889
|
"kind": "audit",
|
|
878
890
|
"schema_version": 7,
|
|
879
|
-
"version": "2.
|
|
891
|
+
"version": "2.90.0",
|
|
880
892
|
"command": "audit",
|
|
881
893
|
"verdict": "fail",
|
|
882
894
|
"changed_files_count": 12,
|
|
@@ -949,7 +961,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
949
961
|
```json
|
|
950
962
|
{
|
|
951
963
|
"schema_version": 7,
|
|
952
|
-
"version": "2.
|
|
964
|
+
"version": "2.90.0",
|
|
953
965
|
"elapsed_ms": 116,
|
|
954
966
|
"feature_flags": [],
|
|
955
967
|
"total_flags": 0
|
|
@@ -964,21 +976,57 @@ Surfaces local security candidates for agent or human verification. The first ru
|
|
|
964
976
|
|
|
965
977
|
Findings are not confirmed vulnerabilities. Use the structural trace to verify whether the value can actually reach client-bundled code. Public env conventions (`NODE_ENV`, `NEXT_PUBLIC_*`, `VITE_*`, `NUXT_PUBLIC_*`, `REACT_APP_*`, `PUBLIC_*`, `GATSBY_*`, `EXPO_PUBLIC_*`, `STORYBOOK_*`) are excluded.
|
|
966
978
|
|
|
967
|
-
The second rule family is a data-driven `tainted-sink` catalogue: syntactic dangerous-sink candidates across
|
|
979
|
+
The second rule family is a data-driven `tainted-sink` catalogue: syntactic dangerous-sink candidates across the catalogue categories listed below. Most rows require a non-literal argument; narrowly literal-aware rows flag deterministic unsafe literals such as wildcard `postMessage` origins, weak crypto algorithms, disabled TLS validation, and JWT algorithm issues. Fallow prefers false-negatives over false-positives.
|
|
968
980
|
|
|
969
981
|
| Category | CWE | Sink |
|
|
970
982
|
|----------|-----|------|
|
|
971
983
|
| `dangerous-html` | 79 | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML` |
|
|
984
|
+
| `template-escape-bypass` | 79 | template-engine `SafeString(...)` wrapping a non-literal value |
|
|
972
985
|
| `command-injection` | 78 | `child_process` `exec` / `execSync` / `spawn` / `spawnSync` (provenance-gated to `node:child_process`) |
|
|
973
986
|
| `code-injection` | 94 | `eval` / `vm.runInNewContext` |
|
|
987
|
+
| `dynamic-regex` | 1333 | `RegExp(...)` / `new RegExp(...)` with a non-literal pattern |
|
|
988
|
+
| `redos-regex` | 1333 | vulnerable regex literals tested with source-backed input |
|
|
989
|
+
| `resource-amplification` | 400 | source-backed size into `Array(...)` / `new Array(...)` / `Buffer.alloc*` / `String.prototype.repeat` / `padStart` / `padEnd` (directly `Math.min`-clamped sizes stay quiet) |
|
|
990
|
+
| `dynamic-module-load` | 95 | dynamic `require(...)` |
|
|
974
991
|
| `sql-injection` | 89 | string concat or interpolated template into `.query()` / `.execute()`, and `sql.raw(...)`. Parameterized `` sql`${x}` `` and the object form `.execute({ sql, args })` are NOT flagged |
|
|
975
|
-
| `ssrf` | 918 | `fetch` / `axios` / `http(s).request` |
|
|
976
|
-
| `path-traversal` | 22 | `
|
|
977
|
-
| `
|
|
992
|
+
| `ssrf` | 918 | `fetch` / `got` / `ky` / `needle` / `request` / `axios` / `superagent` / `undici` / `http(s).request` |
|
|
993
|
+
| `path-traversal` | 22 | `path.join` / `path.resolve` / `node:fs` path methods / route `sendFile` |
|
|
994
|
+
| `header-injection` | 113 | response `setHeader` / `writeHead` |
|
|
995
|
+
| `open-redirect` | 601 | `res.redirect` / `location.href` / `location.assign` / `window.open` |
|
|
996
|
+
| `postmessage-wildcard-origin` | 346 | `postMessage(..., "*")` |
|
|
997
|
+
| `tls-validation-disabled` | 295 | HTTPS/TLS options with `rejectUnauthorized: false`, plus `NODE_TLS_REJECT_UNAUTHORIZED = "0"` |
|
|
998
|
+
| `cleartext-transport` | 319 | cleartext `http://` URLs in fetch-like calls and WebSocket constructors |
|
|
999
|
+
| `electron-unsafe-webpreferences` | 1188 | Electron `webPreferences` with unsafe literal options |
|
|
1000
|
+
| `world-writable-permission` | 732 | `chmod` / `chmodSync` with world-writable modes |
|
|
1001
|
+
| `insecure-temp-file` | 377 | predictable temporary file paths in `fs` writes |
|
|
1002
|
+
| `mysql-multiple-statements` | 89 | MySQL connection options with `multipleStatements: true` |
|
|
1003
|
+
| `permissive-cors` | 942 | CORS wildcard origin with credentials |
|
|
1004
|
+
| `insecure-cookie` | 614 | cookie options missing or disabling `httpOnly` / `secure` |
|
|
1005
|
+
| `mass-assignment` | 915 | source-backed `Object.assign(target, source)` |
|
|
978
1006
|
| `weak-crypto` | 327 | runtime-selectable hash / cipher algorithm |
|
|
1007
|
+
| `deprecated-cipher` | 327 | `crypto.createCipher` / `createDecipher` |
|
|
1008
|
+
| `insecure-randomness` | 338 | `crypto.pseudoRandomBytes(...)` and token-like `Math.random()` use |
|
|
1009
|
+
| `jwt-alg-none` | 347 | JWT signing with algorithm `none` |
|
|
1010
|
+
| `jwt-verify-missing-algorithms` | 347 | `jsonwebtoken` verify calls missing an `algorithms` allowlist |
|
|
1011
|
+
| `unsafe-buffer-alloc` | 1188 | `Buffer.allocUnsafe` / `allocUnsafeSlow` |
|
|
979
1012
|
| `unsafe-deserialization` | 502 | `js-yaml` `load` / `node-serialize` |
|
|
980
|
-
|
|
981
|
-
|
|
1013
|
+
| `angular-trusted-html` | 79 | Angular `bypassSecurityTrust*` |
|
|
1014
|
+
| `nextjs-open-redirect` | 601 | Next.js `redirect` / `permanentRedirect` |
|
|
1015
|
+
| `dom-document-write` | 79 | `document.write` / `document.writeln` |
|
|
1016
|
+
| `jquery-html` | 79 | jQuery `.html(value)` |
|
|
1017
|
+
| `route-send-file` | 22 | Express / Fastify / Hono route `sendFile` |
|
|
1018
|
+
| `webview-injection` | 94 | react-native-webview injected JavaScript |
|
|
1019
|
+
| `prototype-pollution` | 1321 | `__proto__` writes and recursive merge sources |
|
|
1020
|
+
| `zip-slip` | 22 | archive extraction destination paths |
|
|
1021
|
+
| `nosql-injection` | 943 | Mongo / Mongoose query object passthrough |
|
|
1022
|
+
| `ssti` | 1336 | template engine compile / render calls |
|
|
1023
|
+
| `xxe` | 611 | XML parse calls |
|
|
1024
|
+
| `secret-pii-log` | 532 | source-backed secrets or request PII reaching logs |
|
|
1025
|
+
| `hardcoded-secret` | 798 | provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers (include-required) |
|
|
1026
|
+
| `secret-to-network` | 201 | a non-public `process.env` / `import.meta.env` secret reaching a network call body (`fetch` / `axios` / `got` / ...) via same-identifier flow (include-required) |
|
|
1027
|
+
| `xpath-injection` | 643 | `xpath.select` / `select1` with a non-literal expression |
|
|
1028
|
+
|
|
1029
|
+
Build-config and test files are excluded from candidate generation. Security rule families default to `off` and are surfaced only by `fallow security`, never under bare `fallow` or the `audit` gate. Scope which catalogue categories run with `security.categories` include / exclude lists in config. `hardcoded-secret` and `secret-to-network` are intentionally include-required and only run when listed in `security.categories.include` (`secret-to-network` is opt-in because legitimate auth is also a secret reaching a network call). Public-by-convention env vars (`NEXT_PUBLIC_`, `VITE_`, ...) are never treated as secrets.
|
|
982
1030
|
|
|
983
1031
|
### Flags
|
|
984
1032
|
|
|
@@ -991,7 +1039,10 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
991
1039
|
| `--fail-on-issues` | bool | `false` | Exit 1 when candidates are found |
|
|
992
1040
|
| `--sarif-file` | path | none | Write SARIF in addition to the primary output |
|
|
993
1041
|
| `--changed-since` | git ref | none | Scope to candidates whose client anchor or trace hops touch changed files |
|
|
1042
|
+
| `--file` | path, repeatable | none | Scope output to candidates whose finding anchor or trace hop matches the selected file. The full graph is still analyzed |
|
|
994
1043
|
| `--diff-file` | path | none | Scope candidates to added hunks on the client anchor or import trace. Secret-source hops use file-level retention because member-access spans are not yet stored. Use `-` for stdin |
|
|
1044
|
+
| `--diff-stdin` | bool | `false` | Read the unified diff from stdin (equivalent to `--diff-file -`) for line-level scoping and the regression gate |
|
|
1045
|
+
| `--gate` | `new` | none | Fail (exit code **8**) only when the change introduces a NEW security-sink candidate in the changed lines, not on the whole candidate backlog. Requires a diff source (`--changed-since`, `--diff-file`, or `--diff-stdin`); a diff the gate cannot compute is a loud exit 2, never a green gate. Human output says `REVIEW REQUIRED` (not `FAIL`); SARIF keeps every result at `level: note` with the verdict in `run.properties.fallowGate`; `--format json` carries an additive `gate` block (`mode` / `verdict` / `new_count`) |
|
|
995
1046
|
| `--workspace` | string | none | Scope to selected workspace packages |
|
|
996
1047
|
| `--changed-workspaces` | git ref | none | Scope to workspaces changed since a git ref |
|
|
997
1048
|
|
|
@@ -1001,6 +1052,9 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
1001
1052
|
fallow security --format json --quiet
|
|
1002
1053
|
fallow security --ci --sarif-file fallow-security.sarif
|
|
1003
1054
|
git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
1055
|
+
# Regression gate: fail (exit 8) only on candidates introduced in the changed lines
|
|
1056
|
+
fallow security --gate new --changed-since origin/main
|
|
1057
|
+
git diff --cached --unified=0 | fallow security --gate new --diff-stdin
|
|
1004
1058
|
```
|
|
1005
1059
|
|
|
1006
1060
|
### JSON Output Structure
|
|
@@ -1015,7 +1069,17 @@ git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
|
1015
1069
|
}
|
|
1016
1070
|
```
|
|
1017
1071
|
|
|
1018
|
-
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, and `
|
|
1072
|
+
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, and optional `reachability`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`; `client-server-leak` findings omit both. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module; it is ranking and triage context only, not proof that a specific value reaches the sink. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
|
|
1073
|
+
|
|
1074
|
+
Every finding also carries an agent-actionable `candidate { source_kind, sink, boundary }`, an optional `taint_flow { source, sink, path }`, and a stable `finding_id`:
|
|
1075
|
+
|
|
1076
|
+
- `candidate.source_kind`: the untrusted-input kind that reaches the sink, as a stable catalogue id (`"http-request-input"`, `"process-env"`, `"process-argv"`, `"message-event-data"`, `"location-input"`, ...). Absent when no source matched (always absent for `client-server-leak`). Treat an unknown id as an untrusted source of unknown kind; never drop the candidate on that basis.
|
|
1077
|
+
- `candidate.sink`: a self-contained sink (`path`, `line`, `col`, `category`, `cwe`, `callee`), actionable without reading the rest of the finding.
|
|
1078
|
+
- `candidate.boundary`: `client_server` (a `"use client"` file in the trace), `cross_module` (the source reaches the sink across import hops), and optional `architecture_zone` (`from`/`to`) when the anchor also crosses a declared architecture boundary.
|
|
1079
|
+
- `candidate.network`: present only on `secret-to-network` (#890) candidates. `destination` is the network call's URL when it is a static literal (usually intended auth) or absent when the destination is dynamic (the higher-signal exfil case). Use it to triage exfil from intended auth without re-reading source.
|
|
1080
|
+
- There is no `impact` field: deciding exploitability is the verifying agent's job.
|
|
1081
|
+
- `taint_flow`: present only when an untrusted source is import-reachable to the sink. `path` is the compact `{ intra_module, cross_module_hops }` shape; the full ordered hops stay in `reachability.untrusted_source_trace`.
|
|
1082
|
+
- `finding_id`: a stable correlation id, identical across runs for the same rule/path/line and identical to the SARIF `partialFingerprints` value, for tracking a candidate across runs and joining JSON with SARIF.
|
|
1019
1083
|
|
|
1020
1084
|
---
|
|
1021
1085
|
|
|
@@ -1387,8 +1451,10 @@ Available on all commands:
|
|
|
1387
1451
|
| `FALLOW_BIN` | Path to fallow binary (used by the MCP server). |
|
|
1388
1452
|
| `FALLOW_TIMEOUT_SECS` | MCP server subprocess timeout in seconds (default: `120`). Increase for very large codebases. |
|
|
1389
1453
|
| `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
|
|
1454
|
+
| `FALLOW_CACHE_DIR` | Override the persistent extraction cache directory. Wins over `cache.dir`. Useful for read-only checkouts or CI cache volumes. `--no-cache` disables this knob. |
|
|
1390
1455
|
| `FALLOW_CACHE_MAX_SIZE` | Maximum on-disk extraction cache (`.fallow/cache.bin`) size in megabytes (default: `256`). Triggers LRU eviction when crossed. Wins over `cache.maxSizeMb` config field. Intended for CI runners with disk quotas. `--no-cache` short-circuits this knob. |
|
|
1391
1456
|
| `FALLOW_AUDIT_CACHE_MAX_AGE_DAYS` | Max age (in days since last reuse or fresh create) of a persistent reusable `fallow audit` base-snapshot worktree cache. Older entries are reclaimed at the top of the next `fallow audit` invocation (default: `30`). Wins over `audit.cacheMaxAgeDays` config field. `0` disables the GC; invalid values silently fall back to config / default. |
|
|
1457
|
+
| `FALLOW_UPDATE_CHECK` | Set to `off`, `0`, `false`, `disabled`, or `no` to disable the human-TTY upgrade nudge and its background latest-version check. `DO_NOT_TRACK`, `FALLOW_TELEMETRY_DISABLED`, and CI also suppress it. |
|
|
1392
1458
|
| `FALLOW_COMMAND` | GitLab CI: command to run (default: `dead-code`). |
|
|
1393
1459
|
| `FALLOW_FAIL_ON_ISSUES` | GitLab CI: set to `true` to exit 1 if issues found. |
|
|
1394
1460
|
| `FALLOW_CHANGED_SINCE` | GitLab CI: git ref for incremental analysis. Auto-detected in MR pipelines. |
|
|
@@ -1488,7 +1554,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1488
1554
|
{
|
|
1489
1555
|
"kind": "dead-code",
|
|
1490
1556
|
"schema_version": 7,
|
|
1491
|
-
"version": "2.
|
|
1557
|
+
"version": "2.90.0",
|
|
1492
1558
|
"elapsed_ms": 45,
|
|
1493
1559
|
"total_issues": 12,
|
|
1494
1560
|
"entry_points": {
|
|
@@ -1648,7 +1714,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1648
1714
|
{
|
|
1649
1715
|
"kind": "dupes",
|
|
1650
1716
|
"schema_version": 7,
|
|
1651
|
-
"version": "2.
|
|
1717
|
+
"version": "2.90.0",
|
|
1652
1718
|
"elapsed_ms": 82,
|
|
1653
1719
|
"total_clones": 15,
|
|
1654
1720
|
"total_lines_duplicated": 230,
|
|
@@ -1692,11 +1758,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1692
1758
|
{
|
|
1693
1759
|
"kind": "combined",
|
|
1694
1760
|
"schema_version": 7,
|
|
1695
|
-
"version": "2.
|
|
1761
|
+
"version": "2.90.0",
|
|
1696
1762
|
"elapsed_ms": 159,
|
|
1697
1763
|
"check": {
|
|
1698
1764
|
"schema_version": 7,
|
|
1699
|
-
"version": "2.
|
|
1765
|
+
"version": "2.90.0",
|
|
1700
1766
|
"elapsed_ms": 45,
|
|
1701
1767
|
"total_issues": 12,
|
|
1702
1768
|
"unused_files": [],
|
|
@@ -1804,6 +1870,12 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
1804
1870
|
"ignorePatterns": ["**/*.generated.ts"]
|
|
1805
1871
|
},
|
|
1806
1872
|
|
|
1873
|
+
// Extraction cache settings. FALLOW_CACHE_DIR overrides cache.dir.
|
|
1874
|
+
"cache": {
|
|
1875
|
+
"dir": "/tmp/fallow-cache",
|
|
1876
|
+
"maxSizeMb": 256
|
|
1877
|
+
},
|
|
1878
|
+
|
|
1807
1879
|
// Architecture boundaries (preset, custom zones/rules, or auto-discovered feature zones)
|
|
1808
1880
|
// Presets: "layered", "hexagonal", "feature-sliced", "bulletproof"
|
|
1809
1881
|
// Rules accept an optional `allowTypeOnly: [zones]` list that admits type-only imports
|
|
@@ -61,6 +61,8 @@ kind: "coverage-setup"
|
|
|
61
61
|
kind: "coverage-analyze"
|
|
62
62
|
}) | (ListBoundariesOutput & {
|
|
63
63
|
kind: "list-boundaries"
|
|
64
|
+
}) | (WorkspacesOutput & {
|
|
65
|
+
kind: "list-workspaces"
|
|
64
66
|
}) | (HealthOutput & {
|
|
65
67
|
kind: "health"
|
|
66
68
|
}) | (DupesOutput & {
|
|
@@ -353,6 +355,18 @@ export type CoverageTier = ("none" | "partial" | "high")
|
|
|
353
355
|
* Provenance of a CRAP finding's coverage signal.
|
|
354
356
|
*/
|
|
355
357
|
export type CoverageSource = ("istanbul" | "estimated" | "estimated_component_inherited")
|
|
358
|
+
/**
|
|
359
|
+
* Which complexity metric a [`ComplexityContribution`] adds to.
|
|
360
|
+
*/
|
|
361
|
+
export type ComplexityMetric = ("cyclomatic" | "cognitive")
|
|
362
|
+
/**
|
|
363
|
+
* The syntactic construct that produced a single complexity increment.
|
|
364
|
+
*
|
|
365
|
+
* Mirrors `SonarSource` cognitive-complexity vocabulary where it overlaps.
|
|
366
|
+
* `Case` means a `case` label carrying a test; a bare `default` adds nothing
|
|
367
|
+
* to cyclomatic complexity and so produces no contribution.
|
|
368
|
+
*/
|
|
369
|
+
export type ComplexityContributionKind = ("if" | "else" | "else-if" | "ternary" | "logical-and" | "logical-or" | "nullish-coalescing" | "logical-assignment" | "optional-chain" | "for" | "for-in" | "for-of" | "while" | "do-while" | "switch" | "case" | "catch" | "labeled-break" | "labeled-continue")
|
|
356
370
|
/**
|
|
357
371
|
* Discriminant for [`HealthFindingAction::kind`]. Mirrors the action types
|
|
358
372
|
* emitted by `build_health_finding_actions`. A single finding's `actions`
|
|
@@ -587,6 +601,17 @@ export type ImpactTrendDirection = ("improving" | "declining" | "stable")
|
|
|
587
601
|
* from the main contract, mirroring `ImpactReportSchemaVersion`.
|
|
588
602
|
*/
|
|
589
603
|
export type SecuritySchemaVersion = "1"
|
|
604
|
+
/**
|
|
605
|
+
* Gate mode for `fallow security --gate <mode>` (issue #886). Tier 2 reserves
|
|
606
|
+
* the value `newly-reachable`.
|
|
607
|
+
*/
|
|
608
|
+
export type SecurityGateMode = "new"
|
|
609
|
+
/**
|
|
610
|
+
* Gate verdict on the wire. `fail` is the CI-state token; human output renders
|
|
611
|
+
* it as "REVIEW REQUIRED" because these stay unverified candidates, never
|
|
612
|
+
* confirmed vulnerabilities.
|
|
613
|
+
*/
|
|
614
|
+
export type SecurityGateVerdict = ("pass" | "fail")
|
|
590
615
|
/**
|
|
591
616
|
* The kind of security candidate. Findings are CANDIDATES for downstream agent
|
|
592
617
|
* verification, NOT verified vulnerabilities.
|
|
@@ -595,7 +620,20 @@ export type SecurityFindingKind = ("client-server-leak" | "tainted-sink")
|
|
|
595
620
|
/**
|
|
596
621
|
* The role a hop plays in a security finding's structural import trace.
|
|
597
622
|
*/
|
|
598
|
-
export type TraceHopRole = ("client-boundary" | "intermediate" | "secret-source" | "sink")
|
|
623
|
+
export type TraceHopRole = ("client-boundary" | "untrusted-source" | "intermediate" | "secret-source" | "sink")
|
|
624
|
+
/**
|
|
625
|
+
* Dead-code issue kind linked to a security candidate.
|
|
626
|
+
*/
|
|
627
|
+
export type SecurityDeadCodeKind = ("unused-file" | "unused-export")
|
|
628
|
+
/**
|
|
629
|
+
* Runtime coverage state for the function enclosing a security sink.
|
|
630
|
+
* This is production-observation evidence, not an exploitability verdict.
|
|
631
|
+
*/
|
|
632
|
+
export type SecurityRuntimeState = ("runtime-hot" | "runtime-cold" | "never-executed" | "low-traffic" | "coverage-unavailable" | "runtime-unknown")
|
|
633
|
+
/**
|
|
634
|
+
* Defensive control family detected on a source to sink path.
|
|
635
|
+
*/
|
|
636
|
+
export type SecurityControlKind = ("sanitization" | "validation" | "authentication" | "authorization")
|
|
599
637
|
/**
|
|
600
638
|
* Discriminator value for [`CodeClimateIssue::kind`].
|
|
601
639
|
*/
|
|
@@ -1654,6 +1692,15 @@ line: number
|
|
|
1654
1692
|
* 0-based byte column offset of the import that starts the cycle.
|
|
1655
1693
|
*/
|
|
1656
1694
|
col: number
|
|
1695
|
+
/**
|
|
1696
|
+
* Per-file import anchors, one entry per hop in cycle order: `edges[i]`
|
|
1697
|
+
* is the import in `files[i]` pointing to `files[(i + 1) % len]`. Always
|
|
1698
|
+
* the same length as `files`. Drives the per-file LSP diagnostic
|
|
1699
|
+
* squiggly. `#[serde(default)]` so pre-`edges` baselines deserialize;
|
|
1700
|
+
* always emitted on output but intentionally not in the schema's
|
|
1701
|
+
* `required` set (see the struct doc).
|
|
1702
|
+
*/
|
|
1703
|
+
edges?: CircularDependencyEdge[]
|
|
1657
1704
|
/**
|
|
1658
1705
|
* Whether this cycle crosses workspace package boundaries.
|
|
1659
1706
|
*/
|
|
@@ -1669,6 +1716,34 @@ actions: IssueAction[]
|
|
|
1669
1716
|
*/
|
|
1670
1717
|
introduced?: (AuditIntroduced | null)
|
|
1671
1718
|
}
|
|
1719
|
+
/**
|
|
1720
|
+
* One import hop in a circular dependency: the file containing the import
|
|
1721
|
+
* and where that import statement sits.
|
|
1722
|
+
*
|
|
1723
|
+
* `edges[i]` is the import IN `path` (the hop SOURCE, equal to the cycle's
|
|
1724
|
+
* `files[i]`) that points to the NEXT file in the cycle
|
|
1725
|
+
* (`files[(i + 1) % files.len()]`); the target is not repeated here to keep
|
|
1726
|
+
* the wire compact. Enables a per-file diagnostic squiggly anchored under
|
|
1727
|
+
* the offending import rather than a single squiggly on the first file.
|
|
1728
|
+
*
|
|
1729
|
+
* `col` is a 0-based BYTE column, matching the cycle's top-level `col`;
|
|
1730
|
+
* converting it to a UTF-16 code-unit column for LSP clients is a tracked
|
|
1731
|
+
* follow-up shared with the existing field.
|
|
1732
|
+
*/
|
|
1733
|
+
export interface CircularDependencyEdge {
|
|
1734
|
+
/**
|
|
1735
|
+
* The file containing the import (the hop SOURCE; equal to `files[i]`).
|
|
1736
|
+
*/
|
|
1737
|
+
path: string
|
|
1738
|
+
/**
|
|
1739
|
+
* 1-based line number of the import statement pointing to the next file.
|
|
1740
|
+
*/
|
|
1741
|
+
line: number
|
|
1742
|
+
/**
|
|
1743
|
+
* 0-based byte column offset of the import statement.
|
|
1744
|
+
*/
|
|
1745
|
+
col: number
|
|
1746
|
+
}
|
|
1672
1747
|
/**
|
|
1673
1748
|
* Wire-shape envelope for a [`ReExportCycle`] finding. Mirrors
|
|
1674
1749
|
* [`CircularDependencyFinding`]: flattens the bare finding and carries a
|
|
@@ -2200,6 +2275,15 @@ line_count: number
|
|
|
2200
2275
|
* each group in the human listing.
|
|
2201
2276
|
*/
|
|
2202
2277
|
fingerprint: string
|
|
2278
|
+
/**
|
|
2279
|
+
* Best-effort human-readable name for the clone: the dominant repeated
|
|
2280
|
+
* identifier across the duplicated fragment (e.g. a shared `parseCsv`
|
|
2281
|
+
* function). `None` when the clone has no clear dominant name (generic or
|
|
2282
|
+
* tied identifiers); consumers then fall back to a file-based label. Lets
|
|
2283
|
+
* editors and agents label a clone by what it is rather than an opaque
|
|
2284
|
+
* ordinal.
|
|
2285
|
+
*/
|
|
2286
|
+
suggested_name?: (string | null)
|
|
2203
2287
|
/**
|
|
2204
2288
|
* Suggested next steps: an `extract-shared` primary and a
|
|
2205
2289
|
* `suppress-line` secondary. Always emitted (possibly empty for
|
|
@@ -2534,6 +2618,13 @@ coverage_tier?: (CoverageTier | null)
|
|
|
2534
2618
|
coverage_source?: (CoverageSource | null)
|
|
2535
2619
|
inherited_from?: (string | null)
|
|
2536
2620
|
component_rollup?: (ComponentRollup | null)
|
|
2621
|
+
/**
|
|
2622
|
+
* Per-decision-point complexity breakdown explaining WHICH constructs drove
|
|
2623
|
+
* the cyclomatic and cognitive scores. Populated only when the caller opts
|
|
2624
|
+
* in via `health --complexity-breakdown`; empty (and omitted from JSON)
|
|
2625
|
+
* otherwise so default and CI output stay lean.
|
|
2626
|
+
*/
|
|
2627
|
+
contributions?: ComplexityContribution[]
|
|
2537
2628
|
/**
|
|
2538
2629
|
* Machine-actionable fix and suppress hints.
|
|
2539
2630
|
*/
|
|
@@ -2553,6 +2644,36 @@ template_path: string
|
|
|
2553
2644
|
template_cyclomatic: number
|
|
2554
2645
|
template_cognitive: number
|
|
2555
2646
|
}
|
|
2647
|
+
/**
|
|
2648
|
+
* A single complexity increment, located at its source line/column.
|
|
2649
|
+
*
|
|
2650
|
+
* `weight` is the amount this construct added to `metric`; for nested
|
|
2651
|
+
* cognitive increments `weight == 1 + nesting`. Consumers that render inline
|
|
2652
|
+
* (the VS Code editor breakdown) group contributions by `line` and sum the
|
|
2653
|
+
* weights, deferring the per-kind list to a hover.
|
|
2654
|
+
*/
|
|
2655
|
+
export interface ComplexityContribution {
|
|
2656
|
+
/**
|
|
2657
|
+
* 1-based line number where the construct begins.
|
|
2658
|
+
*/
|
|
2659
|
+
line: number
|
|
2660
|
+
/**
|
|
2661
|
+
* 0-based byte column where the construct begins.
|
|
2662
|
+
*/
|
|
2663
|
+
col: number
|
|
2664
|
+
metric: ComplexityMetric
|
|
2665
|
+
kind: ComplexityContributionKind
|
|
2666
|
+
/**
|
|
2667
|
+
* The amount added to `metric` at this site (`1 + nesting` for nested
|
|
2668
|
+
* cognitive increments, otherwise `1`).
|
|
2669
|
+
*/
|
|
2670
|
+
weight: number
|
|
2671
|
+
/**
|
|
2672
|
+
* The nesting depth at the increment site (`0` when not nested). Lets a
|
|
2673
|
+
* consumer explain a cognitive `+3` as "+1 base, +2 nesting".
|
|
2674
|
+
*/
|
|
2675
|
+
nesting: number
|
|
2676
|
+
}
|
|
2556
2677
|
/**
|
|
2557
2678
|
* Suggested action attached to a [`ComplexityViolation`].
|
|
2558
2679
|
*
|
|
@@ -4073,6 +4194,46 @@ allow: string[]
|
|
|
4073
4194
|
*/
|
|
4074
4195
|
allow_type_only?: string[]
|
|
4075
4196
|
}
|
|
4197
|
+
/**
|
|
4198
|
+
* `fallow workspaces --format json` envelope.
|
|
4199
|
+
*/
|
|
4200
|
+
export interface WorkspacesOutput {
|
|
4201
|
+
/**
|
|
4202
|
+
* Number of workspace package entries in `workspaces`.
|
|
4203
|
+
*/
|
|
4204
|
+
workspace_count: number
|
|
4205
|
+
/**
|
|
4206
|
+
* Workspace packages discovered from package manager and tsconfig workspace
|
|
4207
|
+
* declarations. Paths are project-root-relative and use forward slashes.
|
|
4208
|
+
*/
|
|
4209
|
+
workspaces: WorkspaceInfo[]
|
|
4210
|
+
/**
|
|
4211
|
+
* Workspace discovery diagnostics produced while reading workspace
|
|
4212
|
+
* declarations. Present for compatibility with the current wire contract,
|
|
4213
|
+
* even when empty.
|
|
4214
|
+
*/
|
|
4215
|
+
workspace_diagnostics: WorkspaceDiagnostic[]
|
|
4216
|
+
}
|
|
4217
|
+
/**
|
|
4218
|
+
* One workspace package emitted by `fallow workspaces --format json`.
|
|
4219
|
+
*/
|
|
4220
|
+
export interface WorkspaceInfo {
|
|
4221
|
+
/**
|
|
4222
|
+
* Package name from the workspace package.json. This is the value accepted
|
|
4223
|
+
* by `--workspace <name>`.
|
|
4224
|
+
*/
|
|
4225
|
+
name: string
|
|
4226
|
+
/**
|
|
4227
|
+
* Project-root-relative path to the workspace directory, normalized to
|
|
4228
|
+
* forward slashes for cross-platform JSON consumers.
|
|
4229
|
+
*/
|
|
4230
|
+
path: string
|
|
4231
|
+
/**
|
|
4232
|
+
* Whether the package is a generated or platform-specific dependency
|
|
4233
|
+
* package rather than a hand-authored workspace.
|
|
4234
|
+
*/
|
|
4235
|
+
is_internal_dependency: boolean
|
|
4236
|
+
}
|
|
4076
4237
|
/**
|
|
4077
4238
|
* Envelope emitted by `fallow health --format json` (plus the `health` block
|
|
4078
4239
|
* inside the combined and audit envelopes).
|
|
@@ -4696,15 +4857,27 @@ git_sha?: (string | null)
|
|
|
4696
4857
|
timestamp: string
|
|
4697
4858
|
}
|
|
4698
4859
|
/**
|
|
4699
|
-
* The `fallow security --format json` envelope. `
|
|
4700
|
-
*
|
|
4860
|
+
* The `fallow security --format json` envelope. `FallowOutput` discriminates it
|
|
4861
|
+
* by the `kind: "security"` tag; the optional `gate` block is additive and is
|
|
4862
|
+
* not part of that discrimination.
|
|
4701
4863
|
*/
|
|
4702
4864
|
export interface SecurityOutput {
|
|
4703
4865
|
schema_version: SecuritySchemaVersion
|
|
4866
|
+
/**
|
|
4867
|
+
* Gate verdict, present only when `--gate <mode>` was set (issue #886).
|
|
4868
|
+
* Emitted on pass too (`verdict: "pass"`, `new_count: 0`) so consumers
|
|
4869
|
+
* distinguish "gate ran and passed" from "gate did not run" (absent).
|
|
4870
|
+
*/
|
|
4871
|
+
gate?: (SecurityGate | null)
|
|
4704
4872
|
/**
|
|
4705
4873
|
* Security candidates. Paths are project-root-relative, forward-slash.
|
|
4706
4874
|
*/
|
|
4707
4875
|
security_findings: SecurityFinding[]
|
|
4876
|
+
/**
|
|
4877
|
+
* Opt-in attack-surface inventory from untrusted entry points to reachable
|
|
4878
|
+
* sinks. Present only when `--surface` was requested.
|
|
4879
|
+
*/
|
|
4880
|
+
attack_surface?: (SecurityAttackSurfaceEntry[] | null)
|
|
4708
4881
|
/**
|
|
4709
4882
|
* In-band blind spot: number of `"use client"` files whose transitive
|
|
4710
4883
|
* import cone contains a dynamic `import()` the reachability BFS could not
|
|
@@ -4720,6 +4893,18 @@ unresolved_edge_files: number
|
|
|
4720
4893
|
*/
|
|
4721
4894
|
unresolved_callee_sites: number
|
|
4722
4895
|
}
|
|
4896
|
+
/**
|
|
4897
|
+
* The `gate` block on `SecurityOutput`, present only when `--gate <mode>` ran.
|
|
4898
|
+
* Invariant: `verdict == Fail IFF exit code 8 IFF new_count > 0`.
|
|
4899
|
+
*/
|
|
4900
|
+
export interface SecurityGate {
|
|
4901
|
+
mode: SecurityGateMode
|
|
4902
|
+
verdict: SecurityGateVerdict
|
|
4903
|
+
/**
|
|
4904
|
+
* Number of candidates introduced in the changed lines.
|
|
4905
|
+
*/
|
|
4906
|
+
new_count: number
|
|
4907
|
+
}
|
|
4723
4908
|
/**
|
|
4724
4909
|
* A local security CANDIDATE for downstream agent verification, NOT a verified
|
|
4725
4910
|
* vulnerability. Emitted only by `fallow security`, never under bare `fallow`
|
|
@@ -4728,6 +4913,14 @@ unresolved_callee_sites: number
|
|
|
4728
4913
|
* (its hops and length) is the only honest signal.
|
|
4729
4914
|
*/
|
|
4730
4915
|
export interface SecurityFinding {
|
|
4916
|
+
/**
|
|
4917
|
+
* Stable per-finding correlation id, identical across runs for the same
|
|
4918
|
+
* rule + anchor path + line. An autonomous agent that triaged this
|
|
4919
|
+
* candidate on a prior run uses it to correlate the candidate after a
|
|
4920
|
+
* rebase. Equal to the SARIF `partialFingerprints` value for the same
|
|
4921
|
+
* finding (one shared helper computes both).
|
|
4922
|
+
*/
|
|
4923
|
+
finding_id: string
|
|
4731
4924
|
kind: SecurityFindingKind
|
|
4732
4925
|
/**
|
|
4733
4926
|
* The catalogue category id (e.g. `"dangerous-html"`). `None` for
|
|
@@ -4781,12 +4974,37 @@ trace: TraceHop[]
|
|
|
4781
4974
|
*/
|
|
4782
4975
|
actions: IssueAction[]
|
|
4783
4976
|
/**
|
|
4784
|
-
*
|
|
4785
|
-
*
|
|
4786
|
-
*
|
|
4787
|
-
|
|
4977
|
+
* Dead-code cross-link when the same sink candidate sits in code fallow also
|
|
4978
|
+
* reports as removable. Agents should verify the dead-code finding and delete
|
|
4979
|
+
* the code instead of hardening the sink when deletion is safe.
|
|
4980
|
+
*/
|
|
4981
|
+
dead_code?: (SecurityDeadCodeContext | null)
|
|
4982
|
+
/**
|
|
4983
|
+
* Graph-derived reachability ranking signal (issues #860 and #885). `None`
|
|
4984
|
+
* until the post-detection ranking pass fills it; additive on the wire
|
|
4985
|
+
* (skipped when absent). Drives the order findings are emitted in:
|
|
4986
|
+
* runtime-reachable candidates sort first, followed by source-backed and
|
|
4987
|
+
* source-reachable candidates, then wider blast radius.
|
|
4788
4988
|
*/
|
|
4789
4989
|
reachability?: (SecurityReachability | null)
|
|
4990
|
+
candidate: SecurityCandidate
|
|
4991
|
+
/**
|
|
4992
|
+
* Source-to-sink taint-flow triple, present only when an untrusted source
|
|
4993
|
+
* is import-reachable to this sink. Absent (skipped) otherwise.
|
|
4994
|
+
*/
|
|
4995
|
+
taint_flow?: (SecurityTaintFlow | null)
|
|
4996
|
+
/**
|
|
4997
|
+
* Production runtime coverage context for the function enclosing this
|
|
4998
|
+
* security sink. Present only when `fallow security --runtime-coverage`
|
|
4999
|
+
* runs and the candidate is a `tainted-sink`.
|
|
5000
|
+
*/
|
|
5001
|
+
runtime?: (SecurityRuntimeContext | null)
|
|
5002
|
+
/**
|
|
5003
|
+
* Internal projection used by `fallow security --surface`. The CLI strips
|
|
5004
|
+
* this from per-finding JSON and promotes it to the top-level
|
|
5005
|
+
* `attack_surface` field only when requested.
|
|
5006
|
+
*/
|
|
5007
|
+
attack_surface?: (SecurityAttackSurfaceEntry | null)
|
|
4790
5008
|
}
|
|
4791
5009
|
/**
|
|
4792
5010
|
* One hop in a security finding's structural trace. Stored as an absolute path
|
|
@@ -4810,11 +5028,31 @@ line: number
|
|
|
4810
5028
|
col: number
|
|
4811
5029
|
role: TraceHopRole
|
|
4812
5030
|
}
|
|
5031
|
+
/**
|
|
5032
|
+
* Dead-code cross-link attached to a security candidate when fallow's dead-code
|
|
5033
|
+
* pass reports the same anchor as removable code.
|
|
5034
|
+
*/
|
|
5035
|
+
export interface SecurityDeadCodeContext {
|
|
5036
|
+
kind: SecurityDeadCodeKind
|
|
5037
|
+
/**
|
|
5038
|
+
* Unused export name when `kind` is `unused-export`.
|
|
5039
|
+
*/
|
|
5040
|
+
export_name?: (string | null)
|
|
5041
|
+
/**
|
|
5042
|
+
* Dead-code finding line when available.
|
|
5043
|
+
*/
|
|
5044
|
+
line?: (number | null)
|
|
5045
|
+
/**
|
|
5046
|
+
* Agent-facing guidance for deciding between deletion and hardening.
|
|
5047
|
+
*/
|
|
5048
|
+
guidance: string
|
|
5049
|
+
}
|
|
4813
5050
|
/**
|
|
4814
5051
|
* Graph-derived reachability ranking signal for a security candidate. Computed
|
|
4815
|
-
* from the
|
|
4816
|
-
*
|
|
4817
|
-
*
|
|
5052
|
+
* from the existing module graph after detection, never proven exploitable.
|
|
5053
|
+
* Used to surface candidates that sit on a request/runtime-reachable surface,
|
|
5054
|
+
* receive same-module source evidence, or are import-reachable from an
|
|
5055
|
+
* untrusted-source module above isolated helpers or scripts.
|
|
4818
5056
|
*
|
|
4819
5057
|
* This is a relative-ordering signal, NOT a `confidence` or `signal_strength`
|
|
4820
5058
|
* score: fallow does not prove the path is exploitable.
|
|
@@ -4827,6 +5065,23 @@ export interface SecurityReachability {
|
|
|
4827
5065
|
* only from test entry points does not count.
|
|
4828
5066
|
*/
|
|
4829
5067
|
reachable_from_entry: boolean
|
|
5068
|
+
/**
|
|
5069
|
+
* Whether the anchor module is reachable over value imports from a module
|
|
5070
|
+
* that reads a known untrusted input source. Module-level only: this does
|
|
5071
|
+
* not prove a specific source value reaches the sink argument.
|
|
5072
|
+
*/
|
|
5073
|
+
reachable_from_untrusted_source?: boolean
|
|
5074
|
+
/**
|
|
5075
|
+
* Number of value-import hops from the untrusted-source module to the sink
|
|
5076
|
+
* module when `reachable_from_untrusted_source` is true.
|
|
5077
|
+
*/
|
|
5078
|
+
untrusted_source_hop_count?: (number | null)
|
|
5079
|
+
/**
|
|
5080
|
+
* Module-level import path from the untrusted-source module to the sink
|
|
5081
|
+
* anchor. Empty when no source module reaches this candidate. The path is a
|
|
5082
|
+
* ranking explanation, not a value-flow proof.
|
|
5083
|
+
*/
|
|
5084
|
+
untrusted_source_trace?: TraceHop[]
|
|
4830
5085
|
/**
|
|
4831
5086
|
* Number of distinct modules that transitively depend on the anchor module
|
|
4832
5087
|
* (fan-in via the graph's reverse-dependency index). A higher value means a
|
|
@@ -4841,6 +5096,256 @@ blast_radius: number
|
|
|
4841
5096
|
*/
|
|
4842
5097
|
crosses_boundary: boolean
|
|
4843
5098
|
}
|
|
5099
|
+
/**
|
|
5100
|
+
* An agent-actionable candidate record on a [`SecurityFinding`]. fallow fills
|
|
5101
|
+
* `source_kind`, `sink`, and `boundary`. The exploitability IMPACT is
|
|
5102
|
+
* deliberately NOT a field: deciding severity / exploitability is the consuming
|
|
5103
|
+
* agent's job, not fallow's, and a perpetually-null `impact` key would only
|
|
5104
|
+
* train consumers to ignore it. The agent reads this record, then writes its
|
|
5105
|
+
* own impact verdict downstream.
|
|
5106
|
+
*/
|
|
5107
|
+
export interface SecurityCandidate {
|
|
5108
|
+
/**
|
|
5109
|
+
* The kind of untrusted input that reaches the sink, as a stable catalogue
|
|
5110
|
+
* source id (`"http-request-input"`, `"process-env"`, `"process-argv"`,
|
|
5111
|
+
* `"message-event-data"`, `"location-input"`, ...). `None`/absent when no
|
|
5112
|
+
* untrusted source was matched (always `None` for `client-server-leak`).
|
|
5113
|
+
* This is an OPEN string set, driven by the data-driven source catalogue; a
|
|
5114
|
+
* consumer should treat an unknown id as "untrusted source of unknown kind"
|
|
5115
|
+
* and never drop the candidate on that basis.
|
|
5116
|
+
*/
|
|
5117
|
+
source_kind?: (string | null)
|
|
5118
|
+
sink: SecurityCandidateSink
|
|
5119
|
+
boundary: SecurityCandidateBoundary
|
|
5120
|
+
/**
|
|
5121
|
+
* Network-destination context, present only on `secret-to-network` (#890)
|
|
5122
|
+
* candidates: the host the secret-bearing call targets, so an agent can
|
|
5123
|
+
* triage exfil from intended auth. Absent for every other category.
|
|
5124
|
+
*/
|
|
5125
|
+
network?: (SecurityNetworkContext | null)
|
|
5126
|
+
}
|
|
5127
|
+
/**
|
|
5128
|
+
* The sink slot of a [`SecurityCandidate`]: a self-contained description of the
|
|
5129
|
+
* matched sink site. Echoes the finding's own span (`path`/`line`/`col`) plus
|
|
5130
|
+
* the catalogue `category`/`cwe` and the captured `callee`, so an agent can act
|
|
5131
|
+
* on `candidate.sink` in isolation (e.g. after fanning a finding out to a
|
|
5132
|
+
* sub-agent) without reading the parent finding.
|
|
5133
|
+
*/
|
|
5134
|
+
export interface SecurityCandidateSink {
|
|
5135
|
+
/**
|
|
5136
|
+
* File of the sink site. Absolute internally; JSON strips the project root
|
|
5137
|
+
* via `serde_path::serialize`.
|
|
5138
|
+
*/
|
|
5139
|
+
path: string
|
|
5140
|
+
/**
|
|
5141
|
+
* 1-based line of the sink site.
|
|
5142
|
+
*/
|
|
5143
|
+
line: number
|
|
5144
|
+
/**
|
|
5145
|
+
* 0-based byte column of the sink site.
|
|
5146
|
+
*/
|
|
5147
|
+
col: number
|
|
5148
|
+
/**
|
|
5149
|
+
* Catalogue category id of the sink (e.g. `"dangerous-html"`). `None` for
|
|
5150
|
+
* `client-server-leak`.
|
|
5151
|
+
*/
|
|
5152
|
+
category?: (string | null)
|
|
5153
|
+
/**
|
|
5154
|
+
* CWE number declared by the catalogue entry. `None` for
|
|
5155
|
+
* `client-server-leak`; never fabricated beyond the catalogue's value.
|
|
5156
|
+
*/
|
|
5157
|
+
cwe?: (number | null)
|
|
5158
|
+
/**
|
|
5159
|
+
* The sink callee (the dangerous function or member path, e.g.
|
|
5160
|
+
* `"el.innerHTML"`, `"child_process.exec"`) captured by the catalogue match.
|
|
5161
|
+
* `None` for `client-server-leak` and matches that name no callee.
|
|
5162
|
+
*/
|
|
5163
|
+
callee?: (string | null)
|
|
5164
|
+
}
|
|
5165
|
+
/**
|
|
5166
|
+
* The boundary slot of a [`SecurityCandidate`]: which structural boundaries the
|
|
5167
|
+
* candidate's flow crosses. A flow that crosses a client/server or module
|
|
5168
|
+
* boundary is a stronger review target than a self-contained one; the boundary
|
|
5169
|
+
* is fallow's structural signal over a pure source-sink match.
|
|
5170
|
+
*
|
|
5171
|
+
* Two further boundary kinds are RESERVED for a follow-up and are deliberately
|
|
5172
|
+
* absent here rather than emitted as always-false: `export_visibility` (is the
|
|
5173
|
+
* sink on a publicly-exported symbol?) and a package boundary (does the flow
|
|
5174
|
+
* cross an npm-package edge?). Both need new graph derivation that does not
|
|
5175
|
+
* exist today; emitting them as `false` would misreport "we checked and it does
|
|
5176
|
+
* not cross" when fallow has not checked at all.
|
|
5177
|
+
*/
|
|
5178
|
+
export interface SecurityCandidateBoundary {
|
|
5179
|
+
/**
|
|
5180
|
+
* Whether the finding crosses a client/server boundary (a `"use client"`
|
|
5181
|
+
* file appears in the trace). True only for `client-server-leak` today;
|
|
5182
|
+
* `tainted-sink` candidates carry no client/server marker.
|
|
5183
|
+
*/
|
|
5184
|
+
client_server: boolean
|
|
5185
|
+
/**
|
|
5186
|
+
* Whether an untrusted source reaches the sink across one or more
|
|
5187
|
+
* value-import (module) hops. Derived from the reachability hop count.
|
|
5188
|
+
*/
|
|
5189
|
+
cross_module: boolean
|
|
5190
|
+
/**
|
|
5191
|
+
* The architecture-zone crossing when the anchor participates in a declared
|
|
5192
|
+
* boundary-rule violation in the same run. `None` when it crosses no
|
|
5193
|
+
* declared zone boundary.
|
|
5194
|
+
*/
|
|
5195
|
+
architecture_zone?: (SecurityZoneCrossing | null)
|
|
5196
|
+
}
|
|
5197
|
+
/**
|
|
5198
|
+
* A declared architecture-zone crossing, recovered by correlating a finding's
|
|
5199
|
+
* anchor against the run's architecture-boundary violations.
|
|
5200
|
+
*/
|
|
5201
|
+
export interface SecurityZoneCrossing {
|
|
5202
|
+
/**
|
|
5203
|
+
* Zone the importing side belongs to.
|
|
5204
|
+
*/
|
|
5205
|
+
from: string
|
|
5206
|
+
/**
|
|
5207
|
+
* Zone the imported side belongs to.
|
|
5208
|
+
*/
|
|
5209
|
+
to: string
|
|
5210
|
+
}
|
|
5211
|
+
/**
|
|
5212
|
+
* Network-destination context for a `secret-to-network` candidate (#890): where
|
|
5213
|
+
* the secret-bearing network call sends its data. Present only on
|
|
5214
|
+
* network-category candidates. A consuming agent uses it to triage exfil
|
|
5215
|
+
* (dynamic / untrusted destination) from intended auth (a literal provider
|
|
5216
|
+
* host) without re-reading source.
|
|
5217
|
+
*/
|
|
5218
|
+
export interface SecurityNetworkContext {
|
|
5219
|
+
/**
|
|
5220
|
+
* The network call's destination as a static URL string literal, or absent
|
|
5221
|
+
* when the destination is DYNAMIC (not a literal). A dynamic destination is
|
|
5222
|
+
* the higher-signal exfil case; a literal provider host is usually intended
|
|
5223
|
+
* auth.
|
|
5224
|
+
*/
|
|
5225
|
+
destination?: (string | null)
|
|
5226
|
+
}
|
|
5227
|
+
/**
|
|
5228
|
+
* A source-to-sink taint-flow triple, emitted only when an untrusted source is
|
|
5229
|
+
* import-reachable to the sink (`reachability.reachable_from_untrusted_source`).
|
|
5230
|
+
* The `{ source, sink, path }` shape matches the model agent SAST tooling
|
|
5231
|
+
* expects (cf. Semgrep `taint_source` / `taint_sink`, SARIF `threadFlows`).
|
|
5232
|
+
*/
|
|
5233
|
+
export interface SecurityTaintFlow {
|
|
5234
|
+
source: TaintEndpoint
|
|
5235
|
+
sink: TaintEndpoint
|
|
5236
|
+
path: TaintPath
|
|
5237
|
+
}
|
|
5238
|
+
/**
|
|
5239
|
+
* One endpoint (source or sink node) of a [`SecurityTaintFlow`].
|
|
5240
|
+
*/
|
|
5241
|
+
export interface TaintEndpoint {
|
|
5242
|
+
/**
|
|
5243
|
+
* File of the endpoint. Absolute internally; JSON strips the project root.
|
|
5244
|
+
*/
|
|
5245
|
+
path: string
|
|
5246
|
+
/**
|
|
5247
|
+
* 1-based line of the endpoint.
|
|
5248
|
+
*/
|
|
5249
|
+
line: number
|
|
5250
|
+
/**
|
|
5251
|
+
* 0-based byte column of the endpoint.
|
|
5252
|
+
*/
|
|
5253
|
+
col: number
|
|
5254
|
+
}
|
|
5255
|
+
/**
|
|
5256
|
+
* Compact taint-flow path shape. The ordered per-hop trace is NOT duplicated
|
|
5257
|
+
* here: it lives on [`SecurityReachability::untrusted_source_trace`]. This
|
|
5258
|
+
* carries only the flow's structural summary (intra-module flow plus the
|
|
5259
|
+
* cross-module hop count) so consumers do not parse two copies of the hops.
|
|
5260
|
+
*/
|
|
5261
|
+
export interface TaintPath {
|
|
5262
|
+
/**
|
|
5263
|
+
* Whether the source and sink sit in the same module (no import hop between
|
|
5264
|
+
* them); the source-to-sink association is intra-module.
|
|
5265
|
+
*/
|
|
5266
|
+
intra_module: boolean
|
|
5267
|
+
/**
|
|
5268
|
+
* Number of value-import hops from the untrusted-source module to the sink
|
|
5269
|
+
* module. Zero for an intra-module flow.
|
|
5270
|
+
*/
|
|
5271
|
+
cross_module_hops: number
|
|
5272
|
+
}
|
|
5273
|
+
/**
|
|
5274
|
+
* Runtime coverage context attached to a security candidate when
|
|
5275
|
+
* `fallow security --runtime-coverage` is supplied.
|
|
5276
|
+
*/
|
|
5277
|
+
export interface SecurityRuntimeContext {
|
|
5278
|
+
state: SecurityRuntimeState
|
|
5279
|
+
/**
|
|
5280
|
+
* Enclosing function name from static extraction.
|
|
5281
|
+
*/
|
|
5282
|
+
function: string
|
|
5283
|
+
/**
|
|
5284
|
+
* 1-based line where the enclosing function starts.
|
|
5285
|
+
*/
|
|
5286
|
+
line: number
|
|
5287
|
+
/**
|
|
5288
|
+
* Observed invocation count when the runtime report provides it.
|
|
5289
|
+
*/
|
|
5290
|
+
invocations?: (number | null)
|
|
5291
|
+
/**
|
|
5292
|
+
* Runtime coverage stable function id, when available.
|
|
5293
|
+
*/
|
|
5294
|
+
stable_id?: (string | null)
|
|
5295
|
+
/**
|
|
5296
|
+
* Short candidate-framed explanation of the runtime evidence.
|
|
5297
|
+
*/
|
|
5298
|
+
evidence?: (string | null)
|
|
5299
|
+
}
|
|
5300
|
+
/**
|
|
5301
|
+
* One untrusted entry to reachable sink path for `fallow security --surface`.
|
|
5302
|
+
*/
|
|
5303
|
+
export interface SecurityAttackSurfaceEntry {
|
|
5304
|
+
source: TaintEndpoint
|
|
5305
|
+
sink: SecurityCandidateSink
|
|
5306
|
+
/**
|
|
5307
|
+
* Ordered source to sink path. Same shape as the reachability trace so
|
|
5308
|
+
* consumers can reuse existing path handling.
|
|
5309
|
+
*/
|
|
5310
|
+
path: TraceHop[]
|
|
5311
|
+
defensive_boundary: SecurityDefensiveBoundary
|
|
5312
|
+
}
|
|
5313
|
+
/**
|
|
5314
|
+
* Agent-facing defensive-boundary verification context for one surface path.
|
|
5315
|
+
*/
|
|
5316
|
+
export interface SecurityDefensiveBoundary {
|
|
5317
|
+
/**
|
|
5318
|
+
* Known controls detected along this path.
|
|
5319
|
+
*/
|
|
5320
|
+
controls: SecurityDefensiveControl[]
|
|
5321
|
+
/**
|
|
5322
|
+
* Verification question for the consuming agent. It is a prompt, not a
|
|
5323
|
+
* missing-guard verdict.
|
|
5324
|
+
*/
|
|
5325
|
+
verification_prompt: string
|
|
5326
|
+
}
|
|
5327
|
+
/**
|
|
5328
|
+
* Defensive control found on an attack-surface path.
|
|
5329
|
+
*/
|
|
5330
|
+
export interface SecurityDefensiveControl {
|
|
5331
|
+
kind: SecurityControlKind
|
|
5332
|
+
/**
|
|
5333
|
+
* File of the control site. Absolute internally; JSON strips the project root.
|
|
5334
|
+
*/
|
|
5335
|
+
path: string
|
|
5336
|
+
/**
|
|
5337
|
+
* 1-based line of the control site.
|
|
5338
|
+
*/
|
|
5339
|
+
line: number
|
|
5340
|
+
/**
|
|
5341
|
+
* 0-based byte column of the control site.
|
|
5342
|
+
*/
|
|
5343
|
+
col: number
|
|
5344
|
+
/**
|
|
5345
|
+
* Flattened callee path or a stable synthetic guard name.
|
|
5346
|
+
*/
|
|
5347
|
+
callee: string
|
|
5348
|
+
}
|
|
4844
5349
|
/**
|
|
4845
5350
|
* Bare `fallow --format json` envelope.
|
|
4846
5351
|
*/
|