> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fallow.tools/llms.txt
> Use this file to discover all available pages before exploring further.

# fallow security

> CLI reference for fallow security. Surface opt-in local security candidates for agent or human verification.

Surface local security candidates for verification. Two rule families ship: the graph-structural `client-server-leak` (a `"use client"` file that directly reads, or transitively imports a module that reads, a non-public `process.env` secret), and the data-driven `tainted-sink` catalogue (syntactic dangerous-sink candidates across a catalogue of CWE categories). Both default to `off` and run only under `fallow security`.

<Note>
  Findings are candidates, not confirmed vulnerabilities. Fallow reports a structural trace so an agent or human can verify whether the value can actually reach client-bundled code.
</Note>

```bash theme={null}
fallow security
```

## Options

### Output

| Flag                    | Description                                                          |
| :---------------------- | :------------------------------------------------------------------- |
| `-f, --format <FORMAT>` | Output format: `human` (default), `json`, or `sarif`                 |
| `-q, --quiet`           | Suppress progress output                                             |
| `--summary`             | Show a compact human summary instead of per-finding detail           |
| `--ci`                  | CI mode: equivalent to `--format sarif --fail-on-issues --quiet`     |
| `--surface`             | Include the agent-facing `attack_surface[]` inventory in JSON output |
| `--fail-on-issues`      | Exit with code 1 if security candidates are found                    |
| `--sarif-file <PATH>`   | Write SARIF output to a file in addition to the primary output       |

### Scoping

| Flag                                      | Description                                                                                                                                                                                    |
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-r, --root <PATH>`                       | Project root directory (default: current working directory)                                                                                                                                    |
| `-c, --config <PATH>`                     | Path to config file (default: auto-detected)                                                                                                                                                   |
| `--changed-since <REF>` (alias: `--base`) | Only report candidates whose client anchor or trace hops touch files changed since a git ref                                                                                                   |
| `--file <PATH>`                           | Only report candidates whose finding anchor or trace hop matches the selected file. Repeat to select multiple files. The full graph is still analyzed                                          |
| `--diff-file <PATH>`                      | Narrow 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 `-` to read from stdin. |
| `--diff-stdin`                            | Read the unified diff from stdin                                                                                                                                                               |
| `-w, --workspace <PATTERNS>`              | Scope output to selected workspace packages                                                                                                                                                    |
| `--changed-workspaces <REF>`              | Scope output to workspace packages touched since the given git ref                                                                                                                             |

### Performance

| Flag            | Description                 |
| :-------------- | :-------------------------- |
| `--no-cache`    | Disable incremental caching |
| `--threads <N>` | Number of parser threads    |

### Regression gate

| Flag                     | Description                                                                                                                                                                                                                                      |
| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gate new`             | 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`).                     |
| `--gate newly-reachable` | Fail (exit code **8**) when a security candidate that already exists in source becomes reachable from project entry points compared with the base ref. Requires `--changed-since <ref>` because it analyzes the base tree, not only a line diff. |

The gate is the first-line-of-defence form of `fallow security`: a refactor that merely
touches a file already containing a sink passes, while a change that adds a new sink (or
wires a new untrusted source into an existing sink) on a changed line fails. Findings stay
**unverified candidates**: the human output says `REVIEW REQUIRED` (not `FAIL`), SARIF keeps
every result at `level: note` with the verdict in `run.properties.fallowGate`, and
`--format json` carries an additive `gate` block (`mode` / `verdict` / `new_count`).

Use `--gate newly-reachable` for refactors that connect existing sink files to live entry
points. It ignores unreachable candidates in both trees and reports only reachable candidate
keys that are new in the head tree. Diff-only inputs (`--diff-file` / `--diff-stdin`) exit 2
for this mode because there is no base tree to analyze.

Exit codes: **8** = a new candidate was introduced in the changed lines; **0** = clean (or a
docs-only / empty diff); **2** = the gate could not compute the diff (an unfetched ref on a
shallow clone, a bad ref, not a git repo). Exit 8 is dedicated and stable, so a pipeline can
soft-gate it without allow-listing real errors (GitLab `allow_failure: exit_codes: [8]`).

<Note>
  On a shallow clone the merge-base may not be fetched. In GitHub Actions set
  `fetch-depth: 0` on `actions/checkout`; in GitLab CI set `GIT_DEPTH: 0`. The gate exits 2
  (loud) rather than passing silently when it cannot compute the diff.
</Note>

```bash CI gate on changed lines theme={null}
# GitHub Action / generic CI: gate the PR's committed range
fallow security --gate new --changed-since "$BASE_SHA"

# Gate existing sinks that became entry-point reachable
fallow security --gate newly-reachable --changed-since "$BASE_SHA"

# Pre-commit hook: gate the STAGED content (not committed HEAD)
git diff --cached --unified=0 | fallow security --gate new --diff-stdin
```

## Rule: client-server-leak

The detector starts at files with a top-level `"use client"` directive and walks static imports. It reports a candidate when the client boundary can reach a module that reads a non-public `process.env` value.

Public-by-convention env values are excluded:

| Public prefix   | Example                            |
| :-------------- | :--------------------------------- |
| `NODE_ENV`      | `process.env.NODE_ENV`             |
| `NEXT_PUBLIC_*` | `process.env.NEXT_PUBLIC_API_URL`  |
| `VITE_*`        | `process.env.VITE_API_URL`         |
| `NUXT_PUBLIC_*` | `process.env.NUXT_PUBLIC_SITE_URL` |
| `REACT_APP_*`   | `process.env.REACT_APP_API_URL`    |
| `PUBLIC_*`      | `process.env.PUBLIC_SITE_URL`      |
| `GATSBY_*`      | `process.env.GATSBY_SITE_URL`      |
| `EXPO_PUBLIC_*` | `process.env.EXPO_PUBLIC_API_URL`  |
| `STORYBOOK_*`   | `process.env.STORYBOOK_THEME`      |

Dynamic `import()` edges that the graph cannot follow are counted in the output as unresolved edge files. A clean finding list with a non-zero unresolved count is not a clean bill.

The same client cone is checked against a second sink set, reported as the `server-only-import` category: a `"use client"` file that transitively reaches a server-only module. A module counts as server-only when it carries a `"use server"` directive, imports the `server-only` package, `next/server`, `node:fs` / `node:fs/promises`, or `node:child_process` (the `node:` and bare forms both count), or imports a server-only `next/headers` API (`cookies`, `headers`, `draftMode`). The sink set is intentionally narrow to avoid false positives, and a module pulled in only through `next/dynamic(() => import('./x'), { ssr: false })` (the sanctioned client-only escape hatch) is excluded. Findings in this category are labeled distinctly in human output and use the SARIF rule id `security/server-only-import`; the secret-leak and server-only checks are independent, so a client cone that both reads a secret and reaches server-only code produces a finding for each.

## Rule: tainted-sink (catalogue)

A data-driven catalogue of syntactic sink candidates. Where `client-server-leak` is a graph-reachability rule, `tainted-sink` flags a call, member assignment, or tagged template that reaches a known dangerous sink. 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.

All catalogue findings carry `kind: "tainted-sink"` plus a `category` (the catalogue id) and a `cwe` number. The catalogue ships these categories:

| Category                         | CWE  | Sink shape                                                                                                                                                                   |
| :------------------------------- | :--- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dangerous-html`                 | 79   | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML`                                                                                                 |
| `template-escape-bypass`         | 79   | template-engine `SafeString(...)` wrapping a non-literal value                                                                                                               |
| `command-injection`              | 78   | `child_process` `exec` / `execSync` / `spawn` / `spawnSync` (import-provenance gated)                                                                                        |
| `code-injection`                 | 94   | `eval` / `vm.runInNewContext`                                                                                                                                                |
| `dynamic-regex`                  | 1333 | `RegExp(...)` / `new RegExp(...)` with a non-literal pattern                                                                                                                 |
| `redos-regex`                    | 1333 | vulnerable regex literals tested with source-backed input                                                                                                                    |
| `resource-amplification`         | 400  | source-backed size into `Array(...)` / `new Array(...)` / `Buffer.alloc*` / `String.prototype.repeat` / `padStart` / `padEnd` (directly `Math.min`-clamped sizes stay quiet) |
| `dynamic-module-load`            | 95   | dynamic `require(...)`                                                                                                                                                       |
| `sql-injection`                  | 89   | `query` / `execute` with concatenation or interpolation, raw escape hatches (`sql.raw`, Prisma unsafe raw, Knex raw, `sequelize.literal`)                                    |
| `ssrf`                           | 918  | `fetch` / `got` / `ky` / `needle` / `request` / `axios` / `superagent` / `undici` / `http(s).request`                                                                        |
| `path-traversal`                 | 22   | `path.join` / `path.resolve` / `node:fs` path methods / route `sendFile`                                                                                                     |
| `header-injection`               | 113  | response `setHeader` / `writeHead`                                                                                                                                           |
| `open-redirect`                  | 601  | `res.redirect` / `location.href` / `location.assign` / `window.open`                                                                                                         |
| `postmessage-wildcard-origin`    | 346  | `postMessage(..., "*")`                                                                                                                                                      |
| `tls-validation-disabled`        | 295  | HTTPS/TLS options with `rejectUnauthorized: false`, plus `NODE_TLS_REJECT_UNAUTHORIZED = "0"`                                                                                |
| `cleartext-transport`            | 319  | cleartext `http://` URLs in fetch-like calls and WebSocket constructors                                                                                                      |
| `electron-unsafe-webpreferences` | 1188 | Electron `webPreferences` with unsafe literal options                                                                                                                        |
| `world-writable-permission`      | 732  | `chmod` / `chmodSync` with world-writable modes                                                                                                                              |
| `insecure-temp-file`             | 377  | predictable temporary file paths in `fs` writes                                                                                                                              |
| `mysql-multiple-statements`      | 89   | MySQL connection options with `multipleStatements: true`                                                                                                                     |
| `permissive-cors`                | 942  | CORS wildcard origin with credentials                                                                                                                                        |
| `insecure-cookie`                | 614  | cookie options missing or disabling `httpOnly` / `secure`                                                                                                                    |
| `mass-assignment`                | 915  | source-backed `Object.assign(target, source)`                                                                                                                                |
| `weak-crypto`                    | 327  | runtime-selectable hash or cipher algorithm                                                                                                                                  |
| `deprecated-cipher`              | 327  | `crypto.createCipher` / `createDecipher` (no IV, MD5-based KDF)                                                                                                              |
| `insecure-randomness`            | 338  | `crypto.pseudoRandomBytes(...)`                                                                                                                                              |
| `unsafe-buffer-alloc`            | 1188 | `Buffer.allocUnsafe` / `allocUnsafeSlow` (uninitialized memory)                                                                                                              |
| `unsafe-deserialization`         | 502  | `js-yaml` `load` / `node-serialize`                                                                                                                                          |
| `prototype-pollution`            | 1321 | `__proto__` writes and recursive merge sources                                                                                                                               |
| `zip-slip`                       | 22   | archive extraction destination paths                                                                                                                                         |
| `nosql-injection`                | 943  | Mongo / Mongoose query object passthrough                                                                                                                                    |
| `ssti`                           | 1336 | template engine compile / render calls                                                                                                                                       |
| `xxe`                            | 611  | XML parse calls                                                                                                                                                              |
| `secret-pii-log`                 | 532  | source-backed secrets or request PII reaching logs                                                                                                                           |
| `hardcoded-secret`               | 798  | provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers (include-required)                                                               |
| `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)             |
| `llm-call-injection`             | 1427 | an untrusted source reaching the prompt/messages argument of a known LLM-call sink (taint-path gated, pinned to distinctive LLM SDK call shapes)                             |
| `xpath-injection`                | 643  | `xpath.select` / `select1` with a non-literal expression                                                                                                                     |
| `jwt-alg-none`                   | 347  | JWT signing with algorithm `none`                                                                                                                                            |
| `jwt-verify-missing-algorithms`  | 347  | `jsonwebtoken` verify calls missing an `algorithms` allowlist                                                                                                                |
| `webview-injection`              | 94   | react-native-webview `injectJavaScript(...)` / `injectedJavaScript=` (enabler-gated)                                                                                         |
| `angular-trusted-html`           | 79   | Angular `bypassSecurityTrust*` (enabler-gated)                                                                                                                               |
| `nextjs-open-redirect`           | 601  | Next.js `redirect` / `permanentRedirect` (enabler-gated)                                                                                                                     |
| `dom-document-write`             | 79   | `document.write` / `document.writeln`                                                                                                                                        |
| `jquery-html`                    | 79   | jQuery `.html(value)` (enabler-gated)                                                                                                                                        |
| `route-send-file`                | 22   | Express / Fastify / Hono route `sendFile` (enabler-gated)                                                                                                                    |

<Note>
  These are deliberately conservative candidates: a non-literal argument is a signal to verify, not proof of a vulnerability. Fallow does not prove the value is attacker-controlled or reaches the sink unsanitized. Verification is the agent's job.
</Note>

Sink-shaped nodes whose callee cannot be resolved to a static path (dynamic dispatch, computed members, aliased bindings) are counted in the output as `unresolved_callee_sites`. When emitted, `unresolved_callee_diagnostics` adds bounded sample locations, top files, and reason counts for follow-up review. As with `client-server-leak`, a clean finding list with a non-zero count is not a clean bill.

### Sanitizer-aware suppression

Trusted local sanitizer flows do not report. The detector recognizes syntax-proven local HTML escape helpers, renderer helpers whose dynamic HTML fragments are fully sanitizer-backed, and SQL identifier quoting helpers used in identifier positions. Mixed HTML or SQL templates with unsanitized dynamic fragments still report as candidates, and SQL identifier quoting is not treated as value parameterization.

### Enabling categories

`tainted-sink`, `hardcoded-secret`, and `client-server-leak` 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` in config:

```json theme={null}
{
  "security": {
    "categories": {
      "include": ["dangerous-html", "command-injection", "hardcoded-secret"],
      "exclude": []
    }
  }
}
```

With both lists empty, ordinary catalogue categories are active. `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 (sending a bearer token to its own provider) is also a secret reaching a network call; each candidate carries the destination (`candidate.network.destination`, the request URL when it is a literal, or absent when the URL is dynamic) so you can tell exfil from intended auth. Public-by-convention env vars (`NEXT_PUBLIC_`, `VITE_`, `REACT_APP_`, ...) are never treated as secrets.

## Suppression

Suppress a known false positive at file level. Each rule has its own token:

```ts theme={null}
// fallow-ignore-file security-client-server-leak
"use client";
```

```ts theme={null}
// fallow-ignore-file security-sink
const el = document.querySelector(".out");
el.innerHTML = render(userInput);
```

One `security-sink` token covers every catalogue category. Use suppression only after verifying that the value cannot reach the sink unsanitized, for example because the input is a trusted constant, server-only, or sanitized upstream.

## JSON output

`--format json` emits a typed root envelope with `kind: "security"`.

```json theme={null}
{
  "kind": "security",
  "schema_version": "4",
  "version": "3.3.0",
  "elapsed_ms": 42,
  "config": {
    "rules": {
      "security_client_server_leak": {
        "configured": "off",
        "effective": "warn"
      },
      "security_sink": {
        "configured": "off",
        "effective": "warn"
      }
    },
    "categories_include": null,
    "categories_exclude": null
  },
  "security_findings": [],
  "unresolved_edge_files": 0,
  "unresolved_callee_sites": 0,
  "unresolved_callee_diagnostics": null
}
```

`fallow security --summary --format json --quiet` emits the same `kind`, `schema_version`, `version`, `elapsed_ms`, and `config` metadata, but replaces candidate arrays with `summary` aggregate counts:

```json theme={null}
{
  "kind": "security",
  "schema_version": "4",
  "version": "3.3.0",
  "elapsed_ms": 42,
  "config": {
    "rules": {
      "security_client_server_leak": {
        "configured": "off",
        "effective": "warn"
      },
      "security_sink": {
        "configured": "off",
        "effective": "warn"
      }
    },
    "categories_include": null,
    "categories_exclude": null
  },
  "summary": {
    "security_findings": 0,
    "by_severity": {
      "high": 0,
      "medium": 0,
      "low": 0
    },
    "by_category": {},
    "by_reachability": {
      "entry_reachable": 0,
      "untrusted_source_reachable": 0,
      "arg_level": 0,
      "module_level": 0,
      "crosses_boundary": 0,
      "source_backed": 0
    },
    "by_runtime_state": {
      "runtime_hot": 0,
      "runtime_cold": 0,
      "never_executed": 0,
      "low_traffic": 0,
      "coverage_unavailable": 0,
      "runtime_unknown": 0,
      "not_collected": 0
    },
    "unresolved_edge_files": 0,
    "unresolved_callee_sites": 0,
    "attack_surface_entries": 0
  }
}
```

`attack_surface` is present only when you pass `--surface`. Use it when an agent verifier needs source-to-sink path context and defensive-boundary prompts.

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, for example `"dangerous-html"`) and `cwe` (the category's CWE number); `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 `reachability.reachable_from_untrusted_source` is set, `reachability.taint_confidence` tiers the association: `"arg-level"` means the sink argument traces back to a source read in the same module, directly or through up to three chained local bindings (the stronger candidate), and `"module-level"` means the sink only lives in a module reachable from a source over the import graph (the weaker candidate). Tier candidates from this field rather than parsing the `evidence` text. For arg-level findings the trace's first hop points at the line of the actual source read; module-level source hops use the role `"module-source"` and never imply a proven value path.

When present, `unresolved_callee_diagnostics` adds bounded unresolved-callee metadata for follow-up review: `sampled[]` rows with `path`, `line`, `col`, `reason`, and `expression_kind`, `top_files[]` counts, `by_reason[]` counts, and the emitted sample/top-file limits. It is blind-spot metadata, not a finding list, and follows the same `--file`, `--workspace`, `--changed-since`, and `--gate new` scoping as security candidates.

### Agent-actionable candidate record

Every finding also carries a `candidate` record, an optional `taint_flow` triple, and a stable `finding_id`, designed for an agent to act on without re-deriving anything from the evidence string.

```json theme={null}
{
  "finding_id": "9a705395d3b50465",
  "kind": "tainted-sink",
  "category": "command-injection",
  "cwe": 78,
  "path": "src/runner.ts",
  "line": 4,
  "col": 2,
  "candidate": {
    "source_kind": null,
    "sink": {
      "path": "src/runner.ts",
      "line": 4,
      "col": 2,
      "category": "command-injection",
      "cwe": 78,
      "callee": "child_process.exec"
    },
    "boundary": { "client_server": false, "cross_module": true }
  },
  "taint_flow": {
    "source": { "path": "src/route.ts", "line": 1, "col": 9 },
    "sink": { "path": "src/runner.ts", "line": 4, "col": 2 },
    "path": { "intra_module": false, "cross_module_hops": 1 }
  }
}
```

The `candidate` record has three slots:

* `source_kind`: the kind of untrusted input that reaches the sink, as a stable catalogue source id such as `"http-request-input"`, `"process-env"`, `"process-argv"`, `"message-event-data"`, or `"location-input"`. Absent when no untrusted source matched (always absent for `client-server-leak`). Treat an unknown id as an untrusted source of unknown kind; never drop a candidate on that basis.
* `sink`: a self-contained description of the sink site (`path`, `line`, `col`, `category`, `cwe`, and the captured `callee`), so you can act on `candidate.sink` without reading the rest of the finding. URL-category sinks (SSRF, open redirect) may add `url_shape` when it is statically visible: `"fixed-origin-dynamic-path"` (fixed origin with dynamic path or query, the lower-alarm shape) or `"dynamic-origin"` (the origin itself is dynamic).
* `boundary`: whether the flow crosses a `client_server` boundary (a `"use client"` file in the trace) or a `cross_module` boundary (the source reaches the sink across one or more import hops), plus an `architecture_zone` (`from`/`to`) when the anchor also participates in a declared architecture-boundary violation.

There is no `impact` field. Deciding exploitability is the verifying agent's job, not fallow's; fallow flags candidates with structural evidence and a severity-based review-priority tier.

`taint_flow` is present only when an untrusted source is import-reachable to the sink. It is the `{ source, sink, path }` shape agent SAST tooling expects. `path` is a compact summary (`intra_module` and `cross_module_hops`); the full ordered hop list lives in `reachability.untrusted_source_trace` and is not duplicated.

`candidate.network` is present only on `secret-to-network` (#890) candidates. It carries `destination`: the network call's URL when it is a static string literal (usually intended auth, the credential's own provider) or absent when the destination is dynamic (the higher-signal exfil case). Use it to triage exfil from intended auth without re-reading the code.

`finding_id` is a stable correlation id, identical across runs for the same rule, path, and line, and identical to the finding's SARIF `partialFingerprints` value. Use it to track a candidate across runs (for example after a rebase) and to join JSON and SARIF output.

## Subcommands

Two read-only subcommands support the agent-verification loop. They never re-run analysis assumptions into the candidate output: `survivors` joins existing candidates with an external verifier's verdicts, and `blind-spots` reshapes the unresolved-callee diagnostics fallow already produces. See [Security agent verification](/cli/security-agent-verification) for the end-to-end recipe and the `fallow-security-verdict/v1` verdict schema.

### survivors

Join raw `fallow security --format json` candidate output with a verifier's verdict file and render only the candidates the verifier retained as survivors, without rewriting the original candidate fields.

```bash theme={null}
fallow security --format json --quiet > candidates.json
# (run your verifier, writing verdicts.json keyed by finding_id)
fallow security survivors --candidates candidates.json --verdicts verdicts.json
```

| Flag                                   | Description                                                                                                       |
| :------------------------------------- | :---------------------------------------------------------------------------------------------------------------- |
| `--candidates <PATH>`                  | Raw `fallow security --format json` candidate output (required)                                                   |
| `--verdicts <PATH>`                    | Verifier verdict JSON file, one `fallow-security-verdict/v1` entry per `finding_id` (required)                    |
| `--require-verdict-for-each-candidate` | Fail with a structured exit 2 when any candidate has no matching verdict, for CI that must review every candidate |
| `-f, --format <FORMAT>`                | Output format: `human` (default) or `json`                                                                        |
| `-o, --output-file <PATH>`             | Write the report to a file instead of stdout                                                                      |

The output reports `summary.unverdicted` so consumers can distinguish reviewed from unreviewed candidates, and the human renderer separates verifier dispositions from candidates that still need review. Without `--require-verdict-for-each-candidate`, an incomplete verdict file is surfaced (via `summary.unverdicted`) rather than failing.

### blind-spots

Group the unresolved security callees fallow could not resolve (dynamic dispatch, unmodeled framework entry points) into actionable blind-spot output, so a verifier knows where the structural trace stops. `--file` is accepted both before and after the subcommand.

```bash theme={null}
fallow security blind-spots --format json --quiet
```

| Flag                                                         | Description                                                                               |
| :----------------------------------------------------------- | :---------------------------------------------------------------------------------------- |
| `-f, --format <FORMAT>`                                      | Output format: `human` (default) or `json`                                                |
| `--file <PATH>`                                              | Scope diagnostics to selected files (repeatable; accepted before or after the subcommand) |
| `--changed-since <REF>`                                      | Scope to files changed since this git ref                                                 |
| `--diff-file <PATH>` / `--diff-stdin`                        | Unified diff for line-level scoping                                                       |
| `-w, --workspace <WORKSPACE>` / `--changed-workspaces <REF>` | Scope output to selected or recently touched workspaces                                   |
| `-r, --root <ROOT>` / `-c, --config <CONFIG>`                | Project root and config path                                                              |
| `-q, --quiet` / `--no-cache` / `--threads <N>`               | Standard analysis controls                                                                |
| `-o, --output-file <PATH>`                                   | Write the report to a file instead of stdout                                              |

## Examples

<CodeGroup>
  ```bash Basic scan theme={null}
  fallow security
  ```

  ```bash JSON for agents theme={null}
  fallow security --format json --quiet
  ```

  ```bash GitHub Code Scanning theme={null}
  fallow security --ci --sarif-file fallow-security.sarif
  ```

  ```bash PR-scoped scan theme={null}
  git diff --unified=0 origin/main...HEAD | fallow security --diff-file -
  ```
</CodeGroup>
