fallow 2.89.0 → 2.91.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 +82 -18
- package/types/output-contract.d.ts +452 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.91.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.91.0",
|
|
87
|
+
"@fallow-cli/darwin-x64": "2.91.0",
|
|
88
|
+
"@fallow-cli/linux-x64-gnu": "2.91.0",
|
|
89
|
+
"@fallow-cli/linux-arm64-gnu": "2.91.0",
|
|
90
|
+
"@fallow-cli/linux-x64-musl": "2.91.0",
|
|
91
|
+
"@fallow-cli/linux-arm64-musl": "2.91.0",
|
|
92
|
+
"@fallow-cli/win32-arm64-msvc": "2.91.0",
|
|
93
|
+
"@fallow-cli/win32-x64-msvc": "2.91.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`, `--surface`, `--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, severity, evidence, trace, optional `reachability`, and blind-spot counters. `severity` is a review-priority tier, not a verified vulnerability verdict. 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). Set `surface: true` to include top-level `attack_surface[]` entries with defensive-boundary prompts for a verifier. `reachability.untrusted_source_trace` is module-level import context only and does not prove value flow; `reachability.taint_confidence` tiers each reachable candidate as `arg-level` (sink argument traces to a same-module source read, strong) or `module-level` (only the module is import-reachable from a source, weak), so tier from this field instead of the evidence text. Verify trace, reachability context, severity, and evidence before editing code. Supports `root`, `config`, `workspace`, `paths`, `changed_since`, `changed_workspaces`, `surface`, `no_cache`, and `threads`; `paths` forwards repeated `fallow security --file` filters for finding anchors, trace hops, or untrusted-source reachability trace hops. See <https://docs.fallow.tools/cli/security-agent-verification> for the verifier packet and verdict recipe. 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,12 @@ 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`) |
|
|
46
|
+
| `--max-file-size` | int (MB) | `5` | Skip source files larger than N megabytes at discovery instead of parsing them (`0` disables). Guards against the OOM a single multi-MB generated/vendored/bundled file causes on large repos. `.d.ts` files are always analyzed. Skipped files appear on stderr and in `--format json` under `workspace_diagnostics` (`kind: "skipped-large-file"`). Also settable via `FALLOW_MAX_FILE_SIZE`. Global flag. |
|
|
45
47
|
| `--production` | bool | `false` | Exclude test/dev files, only start/build scripts (applies to every analysis) |
|
|
48
|
+
| `--no-production` | bool | `false` | Force production mode off, overriding a project config's `production: true` (applies to every analysis; conflicts with `--production`) |
|
|
46
49
|
| `--production-dead-code` | bool | `false` | Per-analysis production mode for dead-code. Bare combined runs and `fallow audit` only. |
|
|
47
50
|
| `--production-health` | bool | `false` | Per-analysis production mode for health. Bare combined runs and `fallow audit` only. |
|
|
48
51
|
| `--production-dupes` | bool | `false` | Per-analysis production mode for duplication. Bare combined runs and `fallow audit` only. |
|
|
@@ -234,7 +237,7 @@ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entrie
|
|
|
234
237
|
|
|
235
238
|
### On-disk drift protection
|
|
236
239
|
|
|
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
|
|
240
|
+
`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
241
|
|
|
239
242
|
### Low-confidence export removals
|
|
240
243
|
|
|
@@ -243,7 +246,7 @@ Issue #602: `fallow fix` withholds unused-export removals when the consumer may
|
|
|
243
246
|
- **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
247
|
- **Files with an unresolved import.** The file itself imports something fallow could not resolve, so its local usage graph is incomplete.
|
|
245
248
|
|
|
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
|
|
249
|
+
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
250
|
|
|
248
251
|
### File encoding contract
|
|
249
252
|
|
|
@@ -294,7 +297,7 @@ fallow list --workspaces --format json --quiet
|
|
|
294
297
|
fallow workspaces --format json --quiet # alias of `fallow list --workspaces`
|
|
295
298
|
```
|
|
296
299
|
|
|
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
|
|
300
|
+
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
301
|
|
|
299
302
|
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
303
|
|
|
@@ -495,7 +498,7 @@ fallow health --format json --quiet --trend
|
|
|
495
498
|
{
|
|
496
499
|
"kind": "health",
|
|
497
500
|
"schema_version": 7,
|
|
498
|
-
"version": "2.
|
|
501
|
+
"version": "2.91.0",
|
|
499
502
|
"elapsed_ms": 32,
|
|
500
503
|
"summary": {
|
|
501
504
|
"files_analyzed": 482,
|
|
@@ -805,6 +808,7 @@ Audits changed files for dead code, complexity, and duplication. Returns a verdi
|
|
|
805
808
|
| `--base` | string | auto-detect | Git ref to compare against (alias for `--changed-since`) |
|
|
806
809
|
| `--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
810
|
| `--production` | bool | false | Exclude test/story/dev files (applies to dead-code, health, and dupes) |
|
|
811
|
+
| `--no-production` | bool | false | Force production mode off, overriding a project config's `production: true` (conflicts with `--production`) |
|
|
808
812
|
| `--production-dead-code` | bool | false | Per-analysis production mode for the dead-code sub-analysis only |
|
|
809
813
|
| `--production-health` | bool | false | Per-analysis production mode for the health sub-analysis only |
|
|
810
814
|
| `--production-dupes` | bool | false | Per-analysis production mode for the duplication sub-analysis only |
|
|
@@ -885,7 +889,7 @@ fallow audit \
|
|
|
885
889
|
{
|
|
886
890
|
"kind": "audit",
|
|
887
891
|
"schema_version": 7,
|
|
888
|
-
"version": "2.
|
|
892
|
+
"version": "2.91.0",
|
|
889
893
|
"command": "audit",
|
|
890
894
|
"verdict": "fail",
|
|
891
895
|
"changed_files_count": 12,
|
|
@@ -958,7 +962,7 @@ fallow flags --format json --quiet --workspace my-package
|
|
|
958
962
|
```json
|
|
959
963
|
{
|
|
960
964
|
"schema_version": 7,
|
|
961
|
-
"version": "2.
|
|
965
|
+
"version": "2.91.0",
|
|
962
966
|
"elapsed_ms": 116,
|
|
963
967
|
"feature_flags": [],
|
|
964
968
|
"total_flags": 0
|
|
@@ -973,21 +977,57 @@ Surfaces local security candidates for agent or human verification. The first ru
|
|
|
973
977
|
|
|
974
978
|
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
979
|
|
|
976
|
-
The second rule family is a data-driven `tainted-sink` catalogue: syntactic dangerous-sink candidates across
|
|
980
|
+
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
981
|
|
|
978
982
|
| Category | CWE | Sink |
|
|
979
983
|
|----------|-----|------|
|
|
980
984
|
| `dangerous-html` | 79 | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML` |
|
|
985
|
+
| `template-escape-bypass` | 79 | template-engine `SafeString(...)` wrapping a non-literal value |
|
|
981
986
|
| `command-injection` | 78 | `child_process` `exec` / `execSync` / `spawn` / `spawnSync` (provenance-gated to `node:child_process`) |
|
|
982
987
|
| `code-injection` | 94 | `eval` / `vm.runInNewContext` |
|
|
988
|
+
| `dynamic-regex` | 1333 | `RegExp(...)` / `new RegExp(...)` with a non-literal pattern |
|
|
989
|
+
| `redos-regex` | 1333 | vulnerable regex literals tested with source-backed input |
|
|
990
|
+
| `resource-amplification` | 400 | source-backed size into `Array(...)` / `new Array(...)` / `Buffer.alloc*` / `String.prototype.repeat` / `padStart` / `padEnd` (directly `Math.min`-clamped sizes stay quiet) |
|
|
991
|
+
| `dynamic-module-load` | 95 | dynamic `require(...)` |
|
|
983
992
|
| `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
|
-
| `
|
|
993
|
+
| `ssrf` | 918 | `fetch` / `got` / `ky` / `needle` / `request` / `axios` / `superagent` / `undici` / `http(s).request` |
|
|
994
|
+
| `path-traversal` | 22 | `path.join` / `path.resolve` / `node:fs` path methods / route `sendFile` |
|
|
995
|
+
| `header-injection` | 113 | response `setHeader` / `writeHead` |
|
|
996
|
+
| `open-redirect` | 601 | `res.redirect` / `location.href` / `location.assign` / `window.open` |
|
|
997
|
+
| `postmessage-wildcard-origin` | 346 | `postMessage(..., "*")` |
|
|
998
|
+
| `tls-validation-disabled` | 295 | HTTPS/TLS options with `rejectUnauthorized: false`, plus `NODE_TLS_REJECT_UNAUTHORIZED = "0"` |
|
|
999
|
+
| `cleartext-transport` | 319 | cleartext `http://` URLs in fetch-like calls and WebSocket constructors |
|
|
1000
|
+
| `electron-unsafe-webpreferences` | 1188 | Electron `webPreferences` with unsafe literal options |
|
|
1001
|
+
| `world-writable-permission` | 732 | `chmod` / `chmodSync` with world-writable modes |
|
|
1002
|
+
| `insecure-temp-file` | 377 | predictable temporary file paths in `fs` writes |
|
|
1003
|
+
| `mysql-multiple-statements` | 89 | MySQL connection options with `multipleStatements: true` |
|
|
1004
|
+
| `permissive-cors` | 942 | CORS wildcard origin with credentials |
|
|
1005
|
+
| `insecure-cookie` | 614 | cookie options missing or disabling `httpOnly` / `secure` |
|
|
1006
|
+
| `mass-assignment` | 915 | source-backed `Object.assign(target, source)` |
|
|
987
1007
|
| `weak-crypto` | 327 | runtime-selectable hash / cipher algorithm |
|
|
1008
|
+
| `deprecated-cipher` | 327 | `crypto.createCipher` / `createDecipher` |
|
|
1009
|
+
| `insecure-randomness` | 338 | `crypto.pseudoRandomBytes(...)` and token-like `Math.random()` use |
|
|
1010
|
+
| `jwt-alg-none` | 347 | JWT signing with algorithm `none` |
|
|
1011
|
+
| `jwt-verify-missing-algorithms` | 347 | `jsonwebtoken` verify calls missing an `algorithms` allowlist |
|
|
1012
|
+
| `unsafe-buffer-alloc` | 1188 | `Buffer.allocUnsafe` / `allocUnsafeSlow` |
|
|
988
1013
|
| `unsafe-deserialization` | 502 | `js-yaml` `load` / `node-serialize` |
|
|
989
|
-
|
|
990
|
-
|
|
1014
|
+
| `angular-trusted-html` | 79 | Angular `bypassSecurityTrust*` |
|
|
1015
|
+
| `nextjs-open-redirect` | 601 | Next.js `redirect` / `permanentRedirect` |
|
|
1016
|
+
| `dom-document-write` | 79 | `document.write` / `document.writeln` |
|
|
1017
|
+
| `jquery-html` | 79 | jQuery `.html(value)` |
|
|
1018
|
+
| `route-send-file` | 22 | Express / Fastify / Hono route `sendFile` |
|
|
1019
|
+
| `webview-injection` | 94 | react-native-webview injected JavaScript |
|
|
1020
|
+
| `prototype-pollution` | 1321 | `__proto__` writes and recursive merge sources |
|
|
1021
|
+
| `zip-slip` | 22 | archive extraction destination paths |
|
|
1022
|
+
| `nosql-injection` | 943 | Mongo / Mongoose query object passthrough |
|
|
1023
|
+
| `ssti` | 1336 | template engine compile / render calls |
|
|
1024
|
+
| `xxe` | 611 | XML parse calls |
|
|
1025
|
+
| `secret-pii-log` | 532 | source-backed secrets or request PII reaching logs |
|
|
1026
|
+
| `hardcoded-secret` | 798 | provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers (include-required) |
|
|
1027
|
+
| `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) |
|
|
1028
|
+
| `xpath-injection` | 643 | `xpath.select` / `select1` with a non-literal expression |
|
|
1029
|
+
|
|
1030
|
+
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
1031
|
|
|
992
1032
|
### Flags
|
|
993
1033
|
|
|
@@ -1000,7 +1040,11 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
1000
1040
|
| `--fail-on-issues` | bool | `false` | Exit 1 when candidates are found |
|
|
1001
1041
|
| `--sarif-file` | path | none | Write SARIF in addition to the primary output |
|
|
1002
1042
|
| `--changed-since` | git ref | none | Scope to candidates whose client anchor or trace hops touch changed files |
|
|
1043
|
+
| `--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
1044
|
| `--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 |
|
|
1045
|
+
| `--diff-stdin` | bool | `false` | Read the unified diff from stdin (equivalent to `--diff-file -`) for line-level scoping and the regression gate |
|
|
1046
|
+
| `--surface` | bool | `false` | Include the agent-facing `attack_surface[]` inventory in JSON output |
|
|
1047
|
+
| `--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
1048
|
| `--workspace` | string | none | Scope to selected workspace packages |
|
|
1005
1049
|
| `--changed-workspaces` | git ref | none | Scope to workspaces changed since a git ref |
|
|
1006
1050
|
|
|
@@ -1010,6 +1054,9 @@ Build-config and test files are excluded from candidate generation. Both rule fa
|
|
|
1010
1054
|
fallow security --format json --quiet
|
|
1011
1055
|
fallow security --ci --sarif-file fallow-security.sarif
|
|
1012
1056
|
git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
1057
|
+
# Regression gate: fail (exit 8) only on candidates introduced in the changed lines
|
|
1058
|
+
fallow security --gate new --changed-since origin/main
|
|
1059
|
+
git diff --cached --unified=0 | fallow security --gate new --diff-stdin
|
|
1013
1060
|
```
|
|
1014
1061
|
|
|
1015
1062
|
### JSON Output Structure
|
|
@@ -1017,14 +1064,24 @@ git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
|
|
|
1017
1064
|
```json
|
|
1018
1065
|
{
|
|
1019
1066
|
"kind": "security",
|
|
1020
|
-
"schema_version": "
|
|
1067
|
+
"schema_version": "2",
|
|
1021
1068
|
"security_findings": [],
|
|
1022
1069
|
"unresolved_edge_files": 0,
|
|
1023
1070
|
"unresolved_callee_sites": 0
|
|
1024
1071
|
}
|
|
1025
1072
|
```
|
|
1026
1073
|
|
|
1027
|
-
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, and `
|
|
1074
|
+
Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `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. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `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).
|
|
1075
|
+
|
|
1076
|
+
Every finding also carries an agent-actionable `candidate { source_kind, sink, boundary }`, an optional `taint_flow { source, sink, path }`, and a stable `finding_id`:
|
|
1077
|
+
|
|
1078
|
+
- `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.
|
|
1079
|
+
- `candidate.sink`: a self-contained sink (`path`, `line`, `col`, `category`, `cwe`, `callee`), actionable without reading the rest of the finding.
|
|
1080
|
+
- `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.
|
|
1081
|
+
- `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.
|
|
1082
|
+
- There is no `impact` field: deciding exploitability is the verifying agent's job; `severity` is only the review-priority tier.
|
|
1083
|
+
- `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`.
|
|
1084
|
+
- `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
1085
|
|
|
1029
1086
|
---
|
|
1030
1087
|
|
|
@@ -1396,6 +1453,7 @@ Available on all commands:
|
|
|
1396
1453
|
| `FALLOW_BIN` | Path to fallow binary (used by the MCP server). |
|
|
1397
1454
|
| `FALLOW_TIMEOUT_SECS` | MCP server subprocess timeout in seconds (default: `120`). Increase for very large codebases. |
|
|
1398
1455
|
| `FALLOW_EXTENDS_TIMEOUT_SECS` | Timeout for fetching remote config inheritance in seconds (default: `5`). Do not raise this for untrusted sources. |
|
|
1456
|
+
| `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
1457
|
| `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
1458
|
| `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
1459
|
| `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 +1556,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
|
|
|
1498
1556
|
{
|
|
1499
1557
|
"kind": "dead-code",
|
|
1500
1558
|
"schema_version": 7,
|
|
1501
|
-
"version": "2.
|
|
1559
|
+
"version": "2.91.0",
|
|
1502
1560
|
"elapsed_ms": 45,
|
|
1503
1561
|
"total_issues": 12,
|
|
1504
1562
|
"entry_points": {
|
|
@@ -1658,7 +1716,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
|
|
|
1658
1716
|
{
|
|
1659
1717
|
"kind": "dupes",
|
|
1660
1718
|
"schema_version": 7,
|
|
1661
|
-
"version": "2.
|
|
1719
|
+
"version": "2.91.0",
|
|
1662
1720
|
"elapsed_ms": 82,
|
|
1663
1721
|
"total_clones": 15,
|
|
1664
1722
|
"total_lines_duplicated": 230,
|
|
@@ -1702,11 +1760,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
|
|
|
1702
1760
|
{
|
|
1703
1761
|
"kind": "combined",
|
|
1704
1762
|
"schema_version": 7,
|
|
1705
|
-
"version": "2.
|
|
1763
|
+
"version": "2.91.0",
|
|
1706
1764
|
"elapsed_ms": 159,
|
|
1707
1765
|
"check": {
|
|
1708
1766
|
"schema_version": 7,
|
|
1709
|
-
"version": "2.
|
|
1767
|
+
"version": "2.91.0",
|
|
1710
1768
|
"elapsed_ms": 45,
|
|
1711
1769
|
"total_issues": 12,
|
|
1712
1770
|
"unused_files": [],
|
|
@@ -1814,6 +1872,12 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
|
|
|
1814
1872
|
"ignorePatterns": ["**/*.generated.ts"]
|
|
1815
1873
|
},
|
|
1816
1874
|
|
|
1875
|
+
// Extraction cache settings. FALLOW_CACHE_DIR overrides cache.dir.
|
|
1876
|
+
"cache": {
|
|
1877
|
+
"dir": "/tmp/fallow-cache",
|
|
1878
|
+
"maxSizeMb": 256
|
|
1879
|
+
},
|
|
1880
|
+
|
|
1817
1881
|
// Architecture boundaries (preset, custom zones/rules, or auto-discovered feature zones)
|
|
1818
1882
|
// Presets: "layered", "hexagonal", "feature-sliced", "bulletproof"
|
|
1819
1883
|
// 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 & {
|
|
@@ -320,6 +322,12 @@ error: string
|
|
|
320
322
|
kind: "malformed-tsconfig"
|
|
321
323
|
} | {
|
|
322
324
|
kind: "tsconfig-reference-dir-missing"
|
|
325
|
+
} | {
|
|
326
|
+
/**
|
|
327
|
+
* On-disk size of the skipped file in bytes.
|
|
328
|
+
*/
|
|
329
|
+
size_bytes: number
|
|
330
|
+
kind: "skipped-large-file"
|
|
323
331
|
})
|
|
324
332
|
/**
|
|
325
333
|
* Discriminant for [`CloneGroupAction::kind`]. Mirrors the action types
|
|
@@ -598,16 +606,54 @@ export type ImpactTrendDirection = ("improving" | "declining" | "stable")
|
|
|
598
606
|
* The `fallow security --format json` schema version. Independently versioned
|
|
599
607
|
* from the main contract, mirroring `ImpactReportSchemaVersion`.
|
|
600
608
|
*/
|
|
601
|
-
export type SecuritySchemaVersion = "1"
|
|
609
|
+
export type SecuritySchemaVersion = ("1" | "2")
|
|
610
|
+
/**
|
|
611
|
+
* Gate mode for `fallow security --gate <mode>` (issue #886). Tier 2 reserves
|
|
612
|
+
* the value `newly-reachable`.
|
|
613
|
+
*/
|
|
614
|
+
export type SecurityGateMode = "new"
|
|
615
|
+
/**
|
|
616
|
+
* Gate verdict on the wire. `fail` is the CI-state token; human output renders
|
|
617
|
+
* it as "REVIEW REQUIRED" because these stay unverified candidates, never
|
|
618
|
+
* confirmed vulnerabilities.
|
|
619
|
+
*/
|
|
620
|
+
export type SecurityGateVerdict = ("pass" | "fail")
|
|
602
621
|
/**
|
|
603
622
|
* The kind of security candidate. Findings are CANDIDATES for downstream agent
|
|
604
623
|
* verification, NOT verified vulnerabilities.
|
|
605
624
|
*/
|
|
606
625
|
export type SecurityFindingKind = ("client-server-leak" | "tainted-sink")
|
|
626
|
+
/**
|
|
627
|
+
* Verification-priority tier for a security candidate. This is ranking, not an
|
|
628
|
+
* exploitability verdict.
|
|
629
|
+
*/
|
|
630
|
+
export type SecuritySeverity = ("high" | "medium" | "low")
|
|
607
631
|
/**
|
|
608
632
|
* The role a hop plays in a security finding's structural import trace.
|
|
609
633
|
*/
|
|
610
|
-
export type TraceHopRole = ("client-boundary" | "intermediate" | "secret-source" | "sink")
|
|
634
|
+
export type TraceHopRole = ("client-boundary" | "untrusted-source" | "module-source" | "intermediate" | "secret-source" | "sink")
|
|
635
|
+
/**
|
|
636
|
+
* Dead-code issue kind linked to a security candidate.
|
|
637
|
+
*/
|
|
638
|
+
export type SecurityDeadCodeKind = ("unused-file" | "unused-export")
|
|
639
|
+
/**
|
|
640
|
+
* How strongly the untrusted-source signal is associated with the sink, a
|
|
641
|
+
* structured discriminator so a consumer can tier candidates without parsing
|
|
642
|
+
* the human `evidence` prose. Present only when
|
|
643
|
+
* [`SecurityReachability::reachable_from_untrusted_source`] is true. Neither
|
|
644
|
+
* value proves exploitability; both are ranking signals (issue #885 doctrine:
|
|
645
|
+
* rank, never gate).
|
|
646
|
+
*/
|
|
647
|
+
export type TaintConfidence = ("arg-level" | "module-level")
|
|
648
|
+
/**
|
|
649
|
+
* Runtime coverage state for the function enclosing a security sink.
|
|
650
|
+
* This is production-observation evidence, not an exploitability verdict.
|
|
651
|
+
*/
|
|
652
|
+
export type SecurityRuntimeState = ("runtime-hot" | "runtime-cold" | "never-executed" | "low-traffic" | "coverage-unavailable" | "runtime-unknown")
|
|
653
|
+
/**
|
|
654
|
+
* Defensive control family detected on a source to sink path.
|
|
655
|
+
*/
|
|
656
|
+
export type SecurityControlKind = ("sanitization" | "validation" | "authentication" | "authorization")
|
|
611
657
|
/**
|
|
612
658
|
* Discriminator value for [`CodeClimateIssue::kind`].
|
|
613
659
|
*/
|
|
@@ -4168,6 +4214,46 @@ allow: string[]
|
|
|
4168
4214
|
*/
|
|
4169
4215
|
allow_type_only?: string[]
|
|
4170
4216
|
}
|
|
4217
|
+
/**
|
|
4218
|
+
* `fallow workspaces --format json` envelope.
|
|
4219
|
+
*/
|
|
4220
|
+
export interface WorkspacesOutput {
|
|
4221
|
+
/**
|
|
4222
|
+
* Number of workspace package entries in `workspaces`.
|
|
4223
|
+
*/
|
|
4224
|
+
workspace_count: number
|
|
4225
|
+
/**
|
|
4226
|
+
* Workspace packages discovered from package manager and tsconfig workspace
|
|
4227
|
+
* declarations. Paths are project-root-relative and use forward slashes.
|
|
4228
|
+
*/
|
|
4229
|
+
workspaces: WorkspaceInfo[]
|
|
4230
|
+
/**
|
|
4231
|
+
* Workspace discovery diagnostics produced while reading workspace
|
|
4232
|
+
* declarations. Present for compatibility with the current wire contract,
|
|
4233
|
+
* even when empty.
|
|
4234
|
+
*/
|
|
4235
|
+
workspace_diagnostics: WorkspaceDiagnostic[]
|
|
4236
|
+
}
|
|
4237
|
+
/**
|
|
4238
|
+
* One workspace package emitted by `fallow workspaces --format json`.
|
|
4239
|
+
*/
|
|
4240
|
+
export interface WorkspaceInfo {
|
|
4241
|
+
/**
|
|
4242
|
+
* Package name from the workspace package.json. This is the value accepted
|
|
4243
|
+
* by `--workspace <name>`.
|
|
4244
|
+
*/
|
|
4245
|
+
name: string
|
|
4246
|
+
/**
|
|
4247
|
+
* Project-root-relative path to the workspace directory, normalized to
|
|
4248
|
+
* forward slashes for cross-platform JSON consumers.
|
|
4249
|
+
*/
|
|
4250
|
+
path: string
|
|
4251
|
+
/**
|
|
4252
|
+
* Whether the package is a generated or platform-specific dependency
|
|
4253
|
+
* package rather than a hand-authored workspace.
|
|
4254
|
+
*/
|
|
4255
|
+
is_internal_dependency: boolean
|
|
4256
|
+
}
|
|
4171
4257
|
/**
|
|
4172
4258
|
* Envelope emitted by `fallow health --format json` (plus the `health` block
|
|
4173
4259
|
* inside the combined and audit envelopes).
|
|
@@ -4791,15 +4877,27 @@ git_sha?: (string | null)
|
|
|
4791
4877
|
timestamp: string
|
|
4792
4878
|
}
|
|
4793
4879
|
/**
|
|
4794
|
-
* The `fallow security --format json` envelope. `
|
|
4795
|
-
*
|
|
4880
|
+
* The `fallow security --format json` envelope. `FallowOutput` discriminates it
|
|
4881
|
+
* by the `kind: "security"` tag; the optional `gate` block is additive and is
|
|
4882
|
+
* not part of that discrimination.
|
|
4796
4883
|
*/
|
|
4797
4884
|
export interface SecurityOutput {
|
|
4798
4885
|
schema_version: SecuritySchemaVersion
|
|
4886
|
+
/**
|
|
4887
|
+
* Gate verdict, present only when `--gate <mode>` was set (issue #886).
|
|
4888
|
+
* Emitted on pass too (`verdict: "pass"`, `new_count: 0`) so consumers
|
|
4889
|
+
* distinguish "gate ran and passed" from "gate did not run" (absent).
|
|
4890
|
+
*/
|
|
4891
|
+
gate?: (SecurityGate | null)
|
|
4799
4892
|
/**
|
|
4800
4893
|
* Security candidates. Paths are project-root-relative, forward-slash.
|
|
4801
4894
|
*/
|
|
4802
4895
|
security_findings: SecurityFinding[]
|
|
4896
|
+
/**
|
|
4897
|
+
* Opt-in attack-surface inventory from untrusted entry points to reachable
|
|
4898
|
+
* sinks. Present only when `--surface` was requested.
|
|
4899
|
+
*/
|
|
4900
|
+
attack_surface?: (SecurityAttackSurfaceEntry[] | null)
|
|
4803
4901
|
/**
|
|
4804
4902
|
* In-band blind spot: number of `"use client"` files whose transitive
|
|
4805
4903
|
* import cone contains a dynamic `import()` the reachability BFS could not
|
|
@@ -4815,6 +4913,18 @@ unresolved_edge_files: number
|
|
|
4815
4913
|
*/
|
|
4816
4914
|
unresolved_callee_sites: number
|
|
4817
4915
|
}
|
|
4916
|
+
/**
|
|
4917
|
+
* The `gate` block on `SecurityOutput`, present only when `--gate <mode>` ran.
|
|
4918
|
+
* Invariant: `verdict == Fail IFF exit code 8 IFF new_count > 0`.
|
|
4919
|
+
*/
|
|
4920
|
+
export interface SecurityGate {
|
|
4921
|
+
mode: SecurityGateMode
|
|
4922
|
+
verdict: SecurityGateVerdict
|
|
4923
|
+
/**
|
|
4924
|
+
* Number of candidates introduced in the changed lines.
|
|
4925
|
+
*/
|
|
4926
|
+
new_count: number
|
|
4927
|
+
}
|
|
4818
4928
|
/**
|
|
4819
4929
|
* A local security CANDIDATE for downstream agent verification, NOT a verified
|
|
4820
4930
|
* vulnerability. Emitted only by `fallow security`, never under bare `fallow`
|
|
@@ -4823,6 +4933,14 @@ unresolved_callee_sites: number
|
|
|
4823
4933
|
* (its hops and length) is the only honest signal.
|
|
4824
4934
|
*/
|
|
4825
4935
|
export interface SecurityFinding {
|
|
4936
|
+
/**
|
|
4937
|
+
* Stable per-finding correlation id, identical across runs for the same
|
|
4938
|
+
* rule + anchor path + line. An autonomous agent that triaged this
|
|
4939
|
+
* candidate on a prior run uses it to correlate the candidate after a
|
|
4940
|
+
* rebase. Equal to the SARIF `partialFingerprints` value for the same
|
|
4941
|
+
* finding (one shared helper computes both).
|
|
4942
|
+
*/
|
|
4943
|
+
finding_id: string
|
|
4826
4944
|
kind: SecurityFindingKind
|
|
4827
4945
|
/**
|
|
4828
4946
|
* The catalogue category id (e.g. `"dangerous-html"`). `None` for
|
|
@@ -4862,6 +4980,7 @@ evidence: string
|
|
|
4862
4980
|
* `ClientServerLeak`. Skipped from JSON when `false` for output stability.
|
|
4863
4981
|
*/
|
|
4864
4982
|
source_backed?: boolean
|
|
4983
|
+
severity: SecuritySeverity
|
|
4865
4984
|
/**
|
|
4866
4985
|
* Structural import-hop trace from the client boundary to the secret source.
|
|
4867
4986
|
* The hop count is the uncalibrated signal; fallow does not prove the path
|
|
@@ -4876,12 +4995,37 @@ trace: TraceHop[]
|
|
|
4876
4995
|
*/
|
|
4877
4996
|
actions: IssueAction[]
|
|
4878
4997
|
/**
|
|
4879
|
-
*
|
|
4880
|
-
*
|
|
4881
|
-
*
|
|
4882
|
-
|
|
4998
|
+
* Dead-code cross-link when the same sink candidate sits in code fallow also
|
|
4999
|
+
* reports as removable. Agents should verify the dead-code finding and delete
|
|
5000
|
+
* the code instead of hardening the sink when deletion is safe.
|
|
5001
|
+
*/
|
|
5002
|
+
dead_code?: (SecurityDeadCodeContext | null)
|
|
5003
|
+
/**
|
|
5004
|
+
* Graph-derived reachability ranking signal (issues #860 and #885). `None`
|
|
5005
|
+
* until the post-detection ranking pass fills it; additive on the wire
|
|
5006
|
+
* (skipped when absent). Drives the order findings are emitted in:
|
|
5007
|
+
* runtime-reachable candidates sort first, followed by source-backed and
|
|
5008
|
+
* source-reachable candidates, then wider blast radius.
|
|
4883
5009
|
*/
|
|
4884
5010
|
reachability?: (SecurityReachability | null)
|
|
5011
|
+
candidate: SecurityCandidate
|
|
5012
|
+
/**
|
|
5013
|
+
* Source-to-sink taint-flow triple, present only when an untrusted source
|
|
5014
|
+
* is import-reachable to this sink. Absent (skipped) otherwise.
|
|
5015
|
+
*/
|
|
5016
|
+
taint_flow?: (SecurityTaintFlow | null)
|
|
5017
|
+
/**
|
|
5018
|
+
* Production runtime coverage context for the function enclosing this
|
|
5019
|
+
* security sink. Present only when `fallow security --runtime-coverage`
|
|
5020
|
+
* runs and the candidate is a `tainted-sink`.
|
|
5021
|
+
*/
|
|
5022
|
+
runtime?: (SecurityRuntimeContext | null)
|
|
5023
|
+
/**
|
|
5024
|
+
* Internal projection used by `fallow security --surface`. The CLI strips
|
|
5025
|
+
* this from per-finding JSON and promotes it to the top-level
|
|
5026
|
+
* `attack_surface` field only when requested.
|
|
5027
|
+
*/
|
|
5028
|
+
attack_surface?: (SecurityAttackSurfaceEntry | null)
|
|
4885
5029
|
}
|
|
4886
5030
|
/**
|
|
4887
5031
|
* One hop in a security finding's structural trace. Stored as an absolute path
|
|
@@ -4905,11 +5049,31 @@ line: number
|
|
|
4905
5049
|
col: number
|
|
4906
5050
|
role: TraceHopRole
|
|
4907
5051
|
}
|
|
5052
|
+
/**
|
|
5053
|
+
* Dead-code cross-link attached to a security candidate when fallow's dead-code
|
|
5054
|
+
* pass reports the same anchor as removable code.
|
|
5055
|
+
*/
|
|
5056
|
+
export interface SecurityDeadCodeContext {
|
|
5057
|
+
kind: SecurityDeadCodeKind
|
|
5058
|
+
/**
|
|
5059
|
+
* Unused export name when `kind` is `unused-export`.
|
|
5060
|
+
*/
|
|
5061
|
+
export_name?: (string | null)
|
|
5062
|
+
/**
|
|
5063
|
+
* Dead-code finding line when available.
|
|
5064
|
+
*/
|
|
5065
|
+
line?: (number | null)
|
|
5066
|
+
/**
|
|
5067
|
+
* Agent-facing guidance for deciding between deletion and hardening.
|
|
5068
|
+
*/
|
|
5069
|
+
guidance: string
|
|
5070
|
+
}
|
|
4908
5071
|
/**
|
|
4909
5072
|
* Graph-derived reachability ranking signal for a security candidate. Computed
|
|
4910
|
-
* from the
|
|
4911
|
-
*
|
|
4912
|
-
*
|
|
5073
|
+
* from the existing module graph after detection, never proven exploitable.
|
|
5074
|
+
* Used to surface candidates that sit on a request/runtime-reachable surface,
|
|
5075
|
+
* receive same-module source evidence, or are import-reachable from an
|
|
5076
|
+
* untrusted-source module above isolated helpers or scripts.
|
|
4913
5077
|
*
|
|
4914
5078
|
* This is a relative-ordering signal, NOT a `confidence` or `signal_strength`
|
|
4915
5079
|
* score: fallow does not prove the path is exploitable.
|
|
@@ -4922,6 +5086,32 @@ export interface SecurityReachability {
|
|
|
4922
5086
|
* only from test entry points does not count.
|
|
4923
5087
|
*/
|
|
4924
5088
|
reachable_from_entry: boolean
|
|
5089
|
+
/**
|
|
5090
|
+
* Whether the anchor module is reachable over value imports from a module
|
|
5091
|
+
* that reads a known untrusted input source. Module-level only: this does
|
|
5092
|
+
* not prove a specific source value reaches the sink argument.
|
|
5093
|
+
*/
|
|
5094
|
+
reachable_from_untrusted_source?: boolean
|
|
5095
|
+
/**
|
|
5096
|
+
* Structured tier of the untrusted-source association: `arg-level` when the
|
|
5097
|
+
* sink argument traces to a same-module source read (strong), `module-level`
|
|
5098
|
+
* when only the module is import-reachable from a source (weak). Present
|
|
5099
|
+
* exactly when `reachable_from_untrusted_source` is true, so a consumer can
|
|
5100
|
+
* separate strong from weak candidates from this field alone without parsing
|
|
5101
|
+
* the `evidence` string. Not an exploitability proof.
|
|
5102
|
+
*/
|
|
5103
|
+
taint_confidence?: (TaintConfidence | null)
|
|
5104
|
+
/**
|
|
5105
|
+
* Number of value-import hops from the untrusted-source module to the sink
|
|
5106
|
+
* module when `reachable_from_untrusted_source` is true.
|
|
5107
|
+
*/
|
|
5108
|
+
untrusted_source_hop_count?: (number | null)
|
|
5109
|
+
/**
|
|
5110
|
+
* Module-level import path from the untrusted-source module to the sink
|
|
5111
|
+
* anchor. Empty when no source module reaches this candidate. The path is a
|
|
5112
|
+
* ranking explanation, not a value-flow proof.
|
|
5113
|
+
*/
|
|
5114
|
+
untrusted_source_trace?: TraceHop[]
|
|
4925
5115
|
/**
|
|
4926
5116
|
* Number of distinct modules that transitively depend on the anchor module
|
|
4927
5117
|
* (fan-in via the graph's reverse-dependency index). A higher value means a
|
|
@@ -4936,6 +5126,257 @@ blast_radius: number
|
|
|
4936
5126
|
*/
|
|
4937
5127
|
crosses_boundary: boolean
|
|
4938
5128
|
}
|
|
5129
|
+
/**
|
|
5130
|
+
* An agent-actionable candidate record on a [`SecurityFinding`]. fallow fills
|
|
5131
|
+
* `source_kind`, `sink`, and `boundary`. The exploitability IMPACT is
|
|
5132
|
+
* deliberately NOT a field: `severity` on the parent finding is only a
|
|
5133
|
+
* review-priority tier, while deciding exploitability remains the consuming
|
|
5134
|
+
* agent's job. A perpetually-null `impact` key would only train consumers to
|
|
5135
|
+
* ignore it. The agent reads this record, then writes its own impact verdict
|
|
5136
|
+
* downstream.
|
|
5137
|
+
*/
|
|
5138
|
+
export interface SecurityCandidate {
|
|
5139
|
+
/**
|
|
5140
|
+
* The kind of untrusted input that reaches the sink, as a stable catalogue
|
|
5141
|
+
* source id (`"http-request-input"`, `"process-env"`, `"process-argv"`,
|
|
5142
|
+
* `"message-event-data"`, `"location-input"`, ...). `None`/absent when no
|
|
5143
|
+
* untrusted source was matched (always `None` for `client-server-leak`).
|
|
5144
|
+
* This is an OPEN string set, driven by the data-driven source catalogue; a
|
|
5145
|
+
* consumer should treat an unknown id as "untrusted source of unknown kind"
|
|
5146
|
+
* and never drop the candidate on that basis.
|
|
5147
|
+
*/
|
|
5148
|
+
source_kind?: (string | null)
|
|
5149
|
+
sink: SecurityCandidateSink
|
|
5150
|
+
boundary: SecurityCandidateBoundary
|
|
5151
|
+
/**
|
|
5152
|
+
* Network-destination context, present only on `secret-to-network` (#890)
|
|
5153
|
+
* candidates: the host the secret-bearing call targets, so an agent can
|
|
5154
|
+
* triage exfil from intended auth. Absent for every other category.
|
|
5155
|
+
*/
|
|
5156
|
+
network?: (SecurityNetworkContext | null)
|
|
5157
|
+
}
|
|
5158
|
+
/**
|
|
5159
|
+
* The sink slot of a [`SecurityCandidate`]: a self-contained description of the
|
|
5160
|
+
* matched sink site. Echoes the finding's own span (`path`/`line`/`col`) plus
|
|
5161
|
+
* the catalogue `category`/`cwe` and the captured `callee`, so an agent can act
|
|
5162
|
+
* on `candidate.sink` in isolation (e.g. after fanning a finding out to a
|
|
5163
|
+
* sub-agent) without reading the parent finding.
|
|
5164
|
+
*/
|
|
5165
|
+
export interface SecurityCandidateSink {
|
|
5166
|
+
/**
|
|
5167
|
+
* File of the sink site. Absolute internally; JSON strips the project root
|
|
5168
|
+
* via `serde_path::serialize`.
|
|
5169
|
+
*/
|
|
5170
|
+
path: string
|
|
5171
|
+
/**
|
|
5172
|
+
* 1-based line of the sink site.
|
|
5173
|
+
*/
|
|
5174
|
+
line: number
|
|
5175
|
+
/**
|
|
5176
|
+
* 0-based byte column of the sink site.
|
|
5177
|
+
*/
|
|
5178
|
+
col: number
|
|
5179
|
+
/**
|
|
5180
|
+
* Catalogue category id of the sink (e.g. `"dangerous-html"`). `None` for
|
|
5181
|
+
* `client-server-leak`.
|
|
5182
|
+
*/
|
|
5183
|
+
category?: (string | null)
|
|
5184
|
+
/**
|
|
5185
|
+
* CWE number declared by the catalogue entry. `None` for
|
|
5186
|
+
* `client-server-leak`; never fabricated beyond the catalogue's value.
|
|
5187
|
+
*/
|
|
5188
|
+
cwe?: (number | null)
|
|
5189
|
+
/**
|
|
5190
|
+
* The sink callee (the dangerous function or member path, e.g.
|
|
5191
|
+
* `"el.innerHTML"`, `"child_process.exec"`) captured by the catalogue match.
|
|
5192
|
+
* `None` for `client-server-leak` and matches that name no callee.
|
|
5193
|
+
*/
|
|
5194
|
+
callee?: (string | null)
|
|
5195
|
+
}
|
|
5196
|
+
/**
|
|
5197
|
+
* The boundary slot of a [`SecurityCandidate`]: which structural boundaries the
|
|
5198
|
+
* candidate's flow crosses. A flow that crosses a client/server or module
|
|
5199
|
+
* boundary is a stronger review target than a self-contained one; the boundary
|
|
5200
|
+
* is fallow's structural signal over a pure source-sink match.
|
|
5201
|
+
*
|
|
5202
|
+
* Two further boundary kinds are RESERVED for a follow-up and are deliberately
|
|
5203
|
+
* absent here rather than emitted as always-false: `export_visibility` (is the
|
|
5204
|
+
* sink on a publicly-exported symbol?) and a package boundary (does the flow
|
|
5205
|
+
* cross an npm-package edge?). Both need new graph derivation that does not
|
|
5206
|
+
* exist today; emitting them as `false` would misreport "we checked and it does
|
|
5207
|
+
* not cross" when fallow has not checked at all.
|
|
5208
|
+
*/
|
|
5209
|
+
export interface SecurityCandidateBoundary {
|
|
5210
|
+
/**
|
|
5211
|
+
* Whether the finding crosses a client/server boundary (a `"use client"`
|
|
5212
|
+
* file appears in the trace). True only for `client-server-leak` today;
|
|
5213
|
+
* `tainted-sink` candidates carry no client/server marker.
|
|
5214
|
+
*/
|
|
5215
|
+
client_server: boolean
|
|
5216
|
+
/**
|
|
5217
|
+
* Whether an untrusted source reaches the sink across one or more
|
|
5218
|
+
* value-import (module) hops. Derived from the reachability hop count.
|
|
5219
|
+
*/
|
|
5220
|
+
cross_module: boolean
|
|
5221
|
+
/**
|
|
5222
|
+
* The architecture-zone crossing when the anchor participates in a declared
|
|
5223
|
+
* boundary-rule violation in the same run. `None` when it crosses no
|
|
5224
|
+
* declared zone boundary.
|
|
5225
|
+
*/
|
|
5226
|
+
architecture_zone?: (SecurityZoneCrossing | null)
|
|
5227
|
+
}
|
|
5228
|
+
/**
|
|
5229
|
+
* A declared architecture-zone crossing, recovered by correlating a finding's
|
|
5230
|
+
* anchor against the run's architecture-boundary violations.
|
|
5231
|
+
*/
|
|
5232
|
+
export interface SecurityZoneCrossing {
|
|
5233
|
+
/**
|
|
5234
|
+
* Zone the importing side belongs to.
|
|
5235
|
+
*/
|
|
5236
|
+
from: string
|
|
5237
|
+
/**
|
|
5238
|
+
* Zone the imported side belongs to.
|
|
5239
|
+
*/
|
|
5240
|
+
to: string
|
|
5241
|
+
}
|
|
5242
|
+
/**
|
|
5243
|
+
* Network-destination context for a `secret-to-network` candidate (#890): where
|
|
5244
|
+
* the secret-bearing network call sends its data. Present only on
|
|
5245
|
+
* network-category candidates. A consuming agent uses it to triage exfil
|
|
5246
|
+
* (dynamic / untrusted destination) from intended auth (a literal provider
|
|
5247
|
+
* host) without re-reading source.
|
|
5248
|
+
*/
|
|
5249
|
+
export interface SecurityNetworkContext {
|
|
5250
|
+
/**
|
|
5251
|
+
* The network call's destination as a static URL string literal, or absent
|
|
5252
|
+
* when the destination is DYNAMIC (not a literal). A dynamic destination is
|
|
5253
|
+
* the higher-signal exfil case; a literal provider host is usually intended
|
|
5254
|
+
* auth.
|
|
5255
|
+
*/
|
|
5256
|
+
destination?: (string | null)
|
|
5257
|
+
}
|
|
5258
|
+
/**
|
|
5259
|
+
* A source-to-sink taint-flow triple, emitted only when an untrusted source is
|
|
5260
|
+
* import-reachable to the sink (`reachability.reachable_from_untrusted_source`).
|
|
5261
|
+
* The `{ source, sink, path }` shape matches the model agent SAST tooling
|
|
5262
|
+
* expects (cf. Semgrep `taint_source` / `taint_sink`, SARIF `threadFlows`).
|
|
5263
|
+
*/
|
|
5264
|
+
export interface SecurityTaintFlow {
|
|
5265
|
+
source: TaintEndpoint
|
|
5266
|
+
sink: TaintEndpoint
|
|
5267
|
+
path: TaintPath
|
|
5268
|
+
}
|
|
5269
|
+
/**
|
|
5270
|
+
* One endpoint (source or sink node) of a [`SecurityTaintFlow`].
|
|
5271
|
+
*/
|
|
5272
|
+
export interface TaintEndpoint {
|
|
5273
|
+
/**
|
|
5274
|
+
* File of the endpoint. Absolute internally; JSON strips the project root.
|
|
5275
|
+
*/
|
|
5276
|
+
path: string
|
|
5277
|
+
/**
|
|
5278
|
+
* 1-based line of the endpoint.
|
|
5279
|
+
*/
|
|
5280
|
+
line: number
|
|
5281
|
+
/**
|
|
5282
|
+
* 0-based byte column of the endpoint.
|
|
5283
|
+
*/
|
|
5284
|
+
col: number
|
|
5285
|
+
}
|
|
5286
|
+
/**
|
|
5287
|
+
* Compact taint-flow path shape. The ordered per-hop trace is NOT duplicated
|
|
5288
|
+
* here: it lives on [`SecurityReachability::untrusted_source_trace`]. This
|
|
5289
|
+
* carries only the flow's structural summary (intra-module flow plus the
|
|
5290
|
+
* cross-module hop count) so consumers do not parse two copies of the hops.
|
|
5291
|
+
*/
|
|
5292
|
+
export interface TaintPath {
|
|
5293
|
+
/**
|
|
5294
|
+
* Whether the source and sink sit in the same module (no import hop between
|
|
5295
|
+
* them); the source-to-sink association is intra-module.
|
|
5296
|
+
*/
|
|
5297
|
+
intra_module: boolean
|
|
5298
|
+
/**
|
|
5299
|
+
* Number of value-import hops from the untrusted-source module to the sink
|
|
5300
|
+
* module. Zero for an intra-module flow.
|
|
5301
|
+
*/
|
|
5302
|
+
cross_module_hops: number
|
|
5303
|
+
}
|
|
5304
|
+
/**
|
|
5305
|
+
* Runtime coverage context attached to a security candidate when
|
|
5306
|
+
* `fallow security --runtime-coverage` is supplied.
|
|
5307
|
+
*/
|
|
5308
|
+
export interface SecurityRuntimeContext {
|
|
5309
|
+
state: SecurityRuntimeState
|
|
5310
|
+
/**
|
|
5311
|
+
* Enclosing function name from static extraction.
|
|
5312
|
+
*/
|
|
5313
|
+
function: string
|
|
5314
|
+
/**
|
|
5315
|
+
* 1-based line where the enclosing function starts.
|
|
5316
|
+
*/
|
|
5317
|
+
line: number
|
|
5318
|
+
/**
|
|
5319
|
+
* Observed invocation count when the runtime report provides it.
|
|
5320
|
+
*/
|
|
5321
|
+
invocations?: (number | null)
|
|
5322
|
+
/**
|
|
5323
|
+
* Runtime coverage stable function id, when available.
|
|
5324
|
+
*/
|
|
5325
|
+
stable_id?: (string | null)
|
|
5326
|
+
/**
|
|
5327
|
+
* Short candidate-framed explanation of the runtime evidence.
|
|
5328
|
+
*/
|
|
5329
|
+
evidence?: (string | null)
|
|
5330
|
+
}
|
|
5331
|
+
/**
|
|
5332
|
+
* One untrusted entry to reachable sink path for `fallow security --surface`.
|
|
5333
|
+
*/
|
|
5334
|
+
export interface SecurityAttackSurfaceEntry {
|
|
5335
|
+
source: TaintEndpoint
|
|
5336
|
+
sink: SecurityCandidateSink
|
|
5337
|
+
/**
|
|
5338
|
+
* Ordered source to sink path. Same shape as the reachability trace so
|
|
5339
|
+
* consumers can reuse existing path handling.
|
|
5340
|
+
*/
|
|
5341
|
+
path: TraceHop[]
|
|
5342
|
+
defensive_boundary: SecurityDefensiveBoundary
|
|
5343
|
+
}
|
|
5344
|
+
/**
|
|
5345
|
+
* Agent-facing defensive-boundary verification context for one surface path.
|
|
5346
|
+
*/
|
|
5347
|
+
export interface SecurityDefensiveBoundary {
|
|
5348
|
+
/**
|
|
5349
|
+
* Known controls detected along this path.
|
|
5350
|
+
*/
|
|
5351
|
+
controls: SecurityDefensiveControl[]
|
|
5352
|
+
/**
|
|
5353
|
+
* Verification question for the consuming agent. It is a prompt, not a
|
|
5354
|
+
* missing-guard verdict.
|
|
5355
|
+
*/
|
|
5356
|
+
verification_prompt: string
|
|
5357
|
+
}
|
|
5358
|
+
/**
|
|
5359
|
+
* Defensive control found on an attack-surface path.
|
|
5360
|
+
*/
|
|
5361
|
+
export interface SecurityDefensiveControl {
|
|
5362
|
+
kind: SecurityControlKind
|
|
5363
|
+
/**
|
|
5364
|
+
* File of the control site. Absolute internally; JSON strips the project root.
|
|
5365
|
+
*/
|
|
5366
|
+
path: string
|
|
5367
|
+
/**
|
|
5368
|
+
* 1-based line of the control site.
|
|
5369
|
+
*/
|
|
5370
|
+
line: number
|
|
5371
|
+
/**
|
|
5372
|
+
* 0-based byte column of the control site.
|
|
5373
|
+
*/
|
|
5374
|
+
col: number
|
|
5375
|
+
/**
|
|
5376
|
+
* Flattened callee path or a stable synthetic guard name.
|
|
5377
|
+
*/
|
|
5378
|
+
callee: string
|
|
5379
|
+
}
|
|
4939
5380
|
/**
|
|
4940
5381
|
* Bare `fallow --format json` envelope.
|
|
4941
5382
|
*/
|