fallow 2.89.0 → 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 +4 -2
- package/skills/fallow/references/cli-reference.md +79 -17
- package/types/output-contract.d.ts +420 -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,7 +118,7 @@ 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) |
|
|
@@ -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. |
|
|
@@ -234,7 +236,7 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
|
|
|
234
236
|
|
|
235
237
|
### On-disk drift protection
|
|
236
238
|
|
|
237
|
-
`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.
|
|
238
240
|
|
|
239
241
|
### Low-confidence export removals
|
|
240
242
|
|
|
@@ -243,7 +245,7 @@ Issue #602: `fallow fix` withholds unused-export removals when the consumer may
|
|
|
243
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.
|
|
244
246
|
- **Files with an unresolved import.** The file itself imports something fallow could not resolve, so its local usage graph is incomplete.
|
|
245
247
|
|
|
246
|
-
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.
|
|
247
249
|
|
|
248
250
|
### File encoding contract
|
|
249
251
|
|
|
@@ -294,7 +296,7 @@ fallow list --workspaces --format json --quiet
|
|
|
294
296
|
fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
|
|
295
297
|
```
|
|
296
298
|
|
|
297
|
-
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.
|
|
298
300
|
|
|
299
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`.
|
|
300
302
|
|
|
@@ -495,7 +497,7 @@ fallow health --format json --quiet --trend
|
|
|
495
497
|
{
|
|
496
498
|
"kind": "health",
|
|
497
499
|
"schema_version": 7,
|
|
498
|
-
"version": "2.
|
|
500
|
+
"version": "2.90.0",
|
|
499
501
|
"elapsed_ms": 32,
|
|
500
502
|
"summary": {
|
|
501
503
|
"files_analyzed": 482,
|
|
@@ -805,6 +807,7 @@ Audits changed files for dead code, complexity, and duplication. Returns a verdi
|
|
|
805
807
|
| `--base` | string | auto-detect | Git ref to compare against (alias for `--changed-since`) |
|
|
806
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. |
|
|
807
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`) |
|
|
808
811
|
| `--production-dead-code` | bool | false | Per-analysis production mode for the dead-code sub-analysis only |
|
|
809
812
|
| `--production-health` | bool | false | Per-analysis production mode for the health sub-analysis only |
|
|
810
813
|
| `--production-dupes` | bool | false | Per-analysis production mode for the duplication sub-analysis only |
|
|
@@ -885,7 +888,7 @@ fallow audit \
|
|
|
885
888
|
{
|
|
886
889
|
"kind": "audit",
|
|
887
890
|
"schema_version": 7,
|
|
888
|
-
"version": "2.
|
|
891
|
+
"version": "2.90.0",
|
|
889
892
|
"command": "audit",
|
|
890
893
|
"verdict": "fail",
|
|
891
894
|
"changed_files_count": 12,
|
|
@@ -958,7 +961,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
958
961
|
```json
|
|
959
962
|
{
|
|
960
963
|
"schema_version": 7,
|
|
961
|
-
"version": "2.
|
|
964
|
+
"version": "2.90.0",
|
|
962
965
|
"elapsed_ms": 116,
|
|
963
966
|
"feature_flags": [],
|
|
964
967
|
"total_flags": 0
|
|
@@ -973,21 +976,57 @@ Surfaces local security candidates for agent or human verification. The first ru
|
|
|
973
976
|
|
|
974
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.
|
|
975
978
|
|
|
976
|
-
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.
|
|
977
980
|
|
|
978
981
|
| Category | CWE | Sink |
|
|
979
982
|
|----------|-----|------|
|
|
980
983
|
| `dangerous-html` | 79 | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML` |
|
|
984
|
+
| `template-escape-bypass` | 79 | template-engine `SafeString(...)` wrapping a non-literal value |
|
|
981
985
|
| `command-injection` | 78 | `child_process` `exec` / `execSync` / `spawn` / `spawnSync` (provenance-gated to `node:child_process`) |
|
|
982
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(...)` |
|
|
983
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 |
|
|
984
|
-
| `ssrf` | 918 | `fetch` / `axios` / `http(s).request` |
|
|
985
|
-
| `path-traversal` | 22 | `
|
|
986
|
-
| `
|
|
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)` |
|
|
987
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` |
|
|
988
1012
|
| `unsafe-deserialization` | 502 | `js-yaml` `load` / `node-serialize` |
|
|
989
|
-
|
|
990
|
-
|
|
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.
|
|
991
1030
|
|
|
992
1031
|
### Flags
|
|
993
1032
|
|
|
@@ -1000,7 +1039,10 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
1000
1039
|
| `--fail-on-issues` | bool | `false` | Exit 1 when candidates are found |
|
|
1001
1040
|
| `--sarif-file` | path | none | Write SARIF in addition to the primary output |
|
|
1002
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 |
|
|
1003
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`) |
|
|
1004
1046
|
| `--workspace` | string | none | Scope to selected workspace packages |
|
|
1005
1047
|
| `--changed-workspaces` | git ref | none | Scope to workspaces changed since a git ref |
|
|
1006
1048
|
|
|
@@ -1010,6 +1052,9 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
1010
1052
|
fallow security --format json --quiet
|
|
1011
1053
|
fallow security --ci --sarif-file fallow-security.sarif
|
|
1012
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
|
|
1013
1058
|
```
|
|
1014
1059
|
|
|
1015
1060
|
### JSON Output Structure
|
|
@@ -1024,7 +1069,17 @@ git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
|
1024
1069
|
}
|
|
1025
1070
|
```
|
|
1026
1071
|
|
|
1027
|
-
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.
|
|
1028
1083
|
|
|
1029
1084
|
---
|
|
1030
1085
|
|
|
@@ -1396,6 +1451,7 @@ Available on all commands:
|
|
|
1396
1451
|
| `FALLOW_BIN` | Path to fallow binary (used by the MCP server). |
|
|
1397
1452
|
| `FALLOW_TIMEOUT_SECS` | MCP server subprocess timeout in seconds (default: `120`). Increase for very large codebases. |
|
|
1398
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. |
|
|
1399
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. |
|
|
1400
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. |
|
|
1401
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. |
|
|
@@ -1498,7 +1554,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1498
1554
|
{
|
|
1499
1555
|
"kind": "dead-code",
|
|
1500
1556
|
"schema_version": 7,
|
|
1501
|
-
"version": "2.
|
|
1557
|
+
"version": "2.90.0",
|
|
1502
1558
|
"elapsed_ms": 45,
|
|
1503
1559
|
"total_issues": 12,
|
|
1504
1560
|
"entry_points": {
|
|
@@ -1658,7 +1714,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1658
1714
|
{
|
|
1659
1715
|
"kind": "dupes",
|
|
1660
1716
|
"schema_version": 7,
|
|
1661
|
-
"version": "2.
|
|
1717
|
+
"version": "2.90.0",
|
|
1662
1718
|
"elapsed_ms": 82,
|
|
1663
1719
|
"total_clones": 15,
|
|
1664
1720
|
"total_lines_duplicated": 230,
|
|
@@ -1702,11 +1758,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1702
1758
|
{
|
|
1703
1759
|
"kind": "combined",
|
|
1704
1760
|
"schema_version": 7,
|
|
1705
|
-
"version": "2.
|
|
1761
|
+
"version": "2.90.0",
|
|
1706
1762
|
"elapsed_ms": 159,
|
|
1707
1763
|
"check": {
|
|
1708
1764
|
"schema_version": 7,
|
|
1709
|
-
"version": "2.
|
|
1765
|
+
"version": "2.90.0",
|
|
1710
1766
|
"elapsed_ms": 45,
|
|
1711
1767
|
"total_issues": 12,
|
|
1712
1768
|
"unused_files": [],
|
|
@@ -1814,6 +1870,12 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
1814
1870
|
"ignorePatterns": ["**/*.generated.ts"]
|
|
1815
1871
|
},
|
|
1816
1872
|
|
|
1873
|
+
// Extraction cache settings. FALLOW_CACHE_DIR overrides cache.dir.
|
|
1874
|
+
"cache": {
|
|
1875
|
+
"dir": "/tmp/fallow-cache",
|
|
1876
|
+
"maxSizeMb": 256
|
|
1877
|
+
},
|
|
1878
|
+
|
|
1817
1879
|
// Architecture boundaries (preset, custom zones/rules, or auto-discovered feature zones)
|
|
1818
1880
|
// Presets: "layered", "hexagonal", "feature-sliced", "bulletproof"
|
|
1819
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 & {
|
|
@@ -599,6 +601,17 @@ export type ImpactTrendDirection = ("improving" | "declining" | "stable")
|
|
|
599
601
|
* from the main contract, mirroring `ImpactReportSchemaVersion`.
|
|
600
602
|
*/
|
|
601
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")
|
|
602
615
|
/**
|
|
603
616
|
* The kind of security candidate. Findings are CANDIDATES for downstream agent
|
|
604
617
|
* verification, NOT verified vulnerabilities.
|
|
@@ -607,7 +620,20 @@ export type SecurityFindingKind = ("client-server-leak" | "tainted-sink")
|
|
|
607
620
|
/**
|
|
608
621
|
* The role a hop plays in a security finding's structural import trace.
|
|
609
622
|
*/
|
|
610
|
-
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")
|
|
611
637
|
/**
|
|
612
638
|
* Discriminator value for [`CodeClimateIssue::kind`].
|
|
613
639
|
*/
|
|
@@ -4168,6 +4194,46 @@ allow: string[]
|
|
|
4168
4194
|
*/
|
|
4169
4195
|
allow_type_only?: string[]
|
|
4170
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
|
+
}
|
|
4171
4237
|
/**
|
|
4172
4238
|
* Envelope emitted by `fallow health --format json` (plus the `health` block
|
|
4173
4239
|
* inside the combined and audit envelopes).
|
|
@@ -4791,15 +4857,27 @@ git_sha?: (string | null)
|
|
|
4791
4857
|
timestamp: string
|
|
4792
4858
|
}
|
|
4793
4859
|
/**
|
|
4794
|
-
* The `fallow security --format json` envelope. `
|
|
4795
|
-
*
|
|
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.
|
|
4796
4863
|
*/
|
|
4797
4864
|
export interface SecurityOutput {
|
|
4798
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)
|
|
4799
4872
|
/**
|
|
4800
4873
|
* Security candidates. Paths are project-root-relative, forward-slash.
|
|
4801
4874
|
*/
|
|
4802
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)
|
|
4803
4881
|
/**
|
|
4804
4882
|
* In-band blind spot: number of `"use client"` files whose transitive
|
|
4805
4883
|
* import cone contains a dynamic `import()` the reachability BFS could not
|
|
@@ -4815,6 +4893,18 @@ unresolved_edge_files: number
|
|
|
4815
4893
|
*/
|
|
4816
4894
|
unresolved_callee_sites: number
|
|
4817
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
|
+
}
|
|
4818
4908
|
/**
|
|
4819
4909
|
* A local security CANDIDATE for downstream agent verification, NOT a verified
|
|
4820
4910
|
* vulnerability. Emitted only by `fallow security`, never under bare `fallow`
|
|
@@ -4823,6 +4913,14 @@ unresolved_callee_sites: number
|
|
|
4823
4913
|
* (its hops and length) is the only honest signal.
|
|
4824
4914
|
*/
|
|
4825
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
|
|
4826
4924
|
kind: SecurityFindingKind
|
|
4827
4925
|
/**
|
|
4828
4926
|
* The catalogue category id (e.g. `"dangerous-html"`). `None` for
|
|
@@ -4876,12 +4974,37 @@ trace: TraceHop[]
|
|
|
4876
4974
|
*/
|
|
4877
4975
|
actions: IssueAction[]
|
|
4878
4976
|
/**
|
|
4879
|
-
*
|
|
4880
|
-
*
|
|
4881
|
-
*
|
|
4882
|
-
|
|
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.
|
|
4883
4988
|
*/
|
|
4884
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)
|
|
4885
5008
|
}
|
|
4886
5009
|
/**
|
|
4887
5010
|
* One hop in a security finding's structural trace. Stored as an absolute path
|
|
@@ -4905,11 +5028,31 @@ line: number
|
|
|
4905
5028
|
col: number
|
|
4906
5029
|
role: TraceHopRole
|
|
4907
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
|
+
}
|
|
4908
5050
|
/**
|
|
4909
5051
|
* Graph-derived reachability ranking signal for a security candidate. Computed
|
|
4910
|
-
* from the
|
|
4911
|
-
*
|
|
4912
|
-
*
|
|
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.
|
|
4913
5056
|
*
|
|
4914
5057
|
* This is a relative-ordering signal, NOT a `confidence` or `signal_strength`
|
|
4915
5058
|
* score: fallow does not prove the path is exploitable.
|
|
@@ -4922,6 +5065,23 @@ export interface SecurityReachability {
|
|
|
4922
5065
|
* only from test entry points does not count.
|
|
4923
5066
|
*/
|
|
4924
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[]
|
|
4925
5085
|
/**
|
|
4926
5086
|
* Number of distinct modules that transitively depend on the anchor module
|
|
4927
5087
|
* (fan-in via the graph's reverse-dependency index). A higher value means a
|
|
@@ -4936,6 +5096,256 @@ blast_radius: number
|
|
|
4936
5096
|
*/
|
|
4937
5097
|
crosses_boundary: boolean
|
|
4938
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
|
+
}
|
|
4939
5349
|
/**
|
|
4940
5350
|
* Bare `fallow --format json` envelope.
|
|
4941
5351
|
*/
|