Metadata-Version: 2.4
Name: zairo
Version: 0.7.0
Summary: Git-diff-aware security impact analysis: builds a dependency subgraph around changed code and optionally scans it for vulnerabilities with an LLM.
Author-email: iamavu <imailavantika@gmail.com>
License-Expression: MIT
Project-URL: Repository, https://github.com/iamavu/zairo
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.27
Requires-Dist: rich>=15.0
Requires-Dist: jinja2>=3.1
Requires-Dist: litellm>=1.98
Requires-Dist: trailmark>=0.5
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Dynamic: license-file

# zairo
Diff security scanners miss the effects of the changes, Zairo finds those effects and looks for vulnerabilities.
Zairo scans what has changed in your code with context, makes a subgraph for you to look at and finds vulnerabilities using LLMs of your choice.

## Installation

```bash
pipx install zairo
```
## Usage

```bash
# Scan whatever you haven't committed yet
zairo .

# Scan a PR/branch diff
zairo . --base main --target HEAD

# Fail the build if anything high-severity turns up
zairo . --base main --target HEAD --fail-on high
```

Give it more than one repo, as extra arguments or one per line in a `--repos-file` (or both, merged into one list), and it switches to **multi-repo mode** on its own: every repo gets its own report, plus one combined summary.
```bash
zairo backend frontend infra --base main --fail-on high -o zairo_multi_out
```

`--base`/`--target` (and every other option) apply the same way to every repo in the list, so multi-repo mode fits best when they all diff against the same thing (e.g. everyone's `main`). Repos with different conventions need separate runs.

### Flags

**What to scan**

- `--base`, `-b` *(none)*: ref to diff from, e.g. `main` or `HEAD~3`. Left out, `zairo` scans uncommitted changes instead.
- `--target`, `-t` *(none)*: ref to diff to. Needs `--base`; left out (with `--base` set), it diffs against your working tree.
- `--depth`, `-d` *(1)*: how many hops of callers/callees to pull into the impact graph around each change.
- `--language`, `-l` *(auto)*: force a language instead of letting Trailmark auto-detect it.

**LLM scanning**

- `--graph-only` *(off)*: skip the vulnerability scan and only build the impact graph -- no findings, no `report.sarif`.
- `--model` *(`gemini/gemini-2.5-pro`)*: any [LiteLLM model string](https://docs.litellm.ai/docs/providers).
- `--concurrency`, `-c` *(5)*: parallel LLM requests, within one repo's scan.
- `--batch-size` *(1)*: group this many nodes into a single LLM request instead of one call per node -- fewer requests (helps with provider rate limits), at the cost of shared fault isolation: a bad/malformed response fails every node in that batch, not just one. Caching stays per-node either way.
- `--max-tokens` *(4096)*: output budget per request. Reasoning models burn this on internal thinking too, so raise it if you see empty responses.
- `--cache` / `--no-cache` *(cache on)*: skip re-scanning code that's unchanged since the last run (cached by content hash in `<output>/.llm_cache.json`).
- `--tokens` *(off)*: print how many tokens the scan actually used (cache hits don't count, since they made no call).

**Output & gating**

- `--output`, `-o` *(`zairo_out`)*: where the reports go. Multi-repo mode: each repo gets its own `<output>/<repo-slug>/`, plus a combined `rollup.*` here too.
- `--fail-on` *(none)*: exit non-zero if a finding at or above this severity turns up (`low`/`medium`/`high`/`critical`). Errors if combined with `--graph-only` (nothing to gate on). Multi-repo mode: checked across all repos combined. See [CI / PR gating](#ci--pr-gating).
- `--verbose`, `-v` *(off)*: print what's happening step by step (git commands, worktree setup, per-node scan progress).
- `--debug`, `-vv` *(off)*: everything `--verbose` prints, plus the exact prompt sent to the LLM and its raw response for every node -- written to `<output>/debug.log` (per-repo in multi-repo mode), since it's too much to print to the console.

**Multi-repo mode only**

- `--repos-file` *(none)*: one repo path per line (`#` comments allowed), merged with any repos given directly.
- `--repo-concurrency` *(1)*: how many repos to scan at once. Total in-flight LLM requests can reach `--concurrency` × `--repo-concurrency`, so mind your provider's rate limits. Above 1, progress prints one summary line per repo on completion instead of live step-by-step detail.
- `--continue-on-error` / `--stop-on-error` *(continue)*: keep scanning the rest of the list, or stop, when one repo fails. Either way, any failed repo still fails the overall exit code.

Run `zairo --help` any time for this same list from the CLI.

## Output files

- **`report.json`** *(always)*: the raw impact graph (nodes, edges, and any attached findings), as data.
- **`report.html`** *(always)*: a self-contained, interactive dependency-graph viewer (Cytoscape.js). Click a node to see its findings.
- **`report.sarif`** *(unless `--graph-only` is used)*: findings in [SARIF 2.1.0](https://sarifweb.azurewebsites.net/), for GitHub code scanning or any other SARIF consumer. Always written, even for a clean scan (an empty-but-valid log), so a scanning UI can mark previously reported alerts resolved. Findings are grouped into rules by CWE when the model tagged one, so recurring issues of the same kind collapse into one rule instead of a new one per wording variant.

Multi-repo mode produces the same three files per repo, plus `rollup.json` / `rollup.html` / `rollup.sarif`: per-repo status and severity counts, a dashboard table linking into each repo's reports, and every repo's SARIF results merged into one multi-run log.

### Deleted code

A function/class/module removed entirely (not just edited) still shows up in `report.html`, with status `deleted`: a dashed, faded node marking where it used to live. Trailmark's graph can't represent this on its own (it only ever reflects the tree as it stands now), so `zairo` detects deletions separately: it also parses the changed files as they existed at `--base` (or `HEAD`, if `--base` wasn't given) and diffs the two symbol sets. A deleted function is never sent to the LLM scanner (there's no live code left to scan), so it carries only its name, kind, and former location, never findings.

## CI / PR gating

`--fail-on <low|medium|high|critical>` exits non-zero if any finding at or
above that severity is found (across all repos combined, in multi-repo
mode), so a CI step can block a merge on it. A couple of things worth
knowing:

- It errors if combined with `--graph-only` (there'd be nothing to gate on).
- It never suppresses the SARIF output: that's still written even on a
  failed gate, so a scanning UI reflects the current state either way.

```bash
zairo . --base "$BASE_REF" --target HEAD --fail-on high -o zairo_out
```

See [examples/github-actions/zairo-pr-scan.yml](examples/github-actions/zairo-pr-scan.yml)
for a full PR-scan workflow: it runs zairo on the PR diff, uploads
`report.sarif` to GitHub's code scanning, and fails the job if the gate
fails.
