Metadata-Version: 2.5
Name: bezoar
Version: 1.0.0a2
Summary: Open-source security scanner for the AI-agent supply chain — skills, MCPs, plugins, subagents
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: pyyaml>=6.0
Provides-Extra: console
Requires-Dist: bezoar-console==1.0.0a2; extra == 'console'
Provides-Extra: dev
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: jsonschema; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-xdist; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: sbom
Requires-Dist: cyclonedx-bom; extra == 'sbom'
Description-Content-Type: text/markdown

# bezoar

**The open-source security scanner for the AI-agent supply chain** — skills, MCP servers,
plugins, subagents, and CLI add-ons across every major agent platform.

> *Bezoar: the legendary stone believed to neutralize any poison. This one neutralizes
> poisoned agent add-ons.*

`bezoar scan` audits an add-on before it enters your agent's context: static detectors,
four verdict **bands**, and a policy layer that turns those bands into **decisions**.
Think `npm audit` for agent add-ons — offline-first, deterministic, CI-friendly,
SARIF/JSON output.

[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/bezoar.svg)](https://pypi.org/project/bezoar/)
[![CI](https://github.com/IsmailKharoub/bezoar/actions/workflows/ci.yml/badge.svg)](https://github.com/IsmailKharoub/bezoar/actions/workflows/ci.yml)
[![SARIF](https://img.shields.io/badge/SARIF-2.1.0-informational)](docs/ci.md)

**1.0.0a2** ships the scanner, CLI, SARIF/JSON reporters, GitHub Action, and web
console. The commands below and the guides in `docs/` describe that shipped
surface.

## 60-second quickstart

Requires Python ≥ 3.11.

```bash
# From this checkout (works before PyPI)
uv tool install .

# After the 1.0.0a2 tag is published
# uv tool install bezoar==1.0.0a2

bezoar version
bezoar scan tests/corpus/benign/claude_code/skill-markdown-lint --fail-on unsafe
```

Point `bezoar scan` at any skill directory or repo root. Flags: `--format
text|json|sarif` (reports go to stdout; there is no `--output`), `--fail-on`,
`--config`, `--changed-since`, `--baseline`. Exit `0` = gate passed, `1` =
band ≥ `--fail-on`, `2` usage, `3` incomplete, `4` internal.

See [Getting started](docs/getting-started.md) for reading findings.

### Illustrative text report

The live text reporter prints a band table, then per-component rows.
The excerpt below is the finding shape: `band` is a fact; `decision` is
policy; locations are raw `file:line:column`; rule ids use `<family>/<rule>`.

```text
✗  band=malicious  decision=block  components=1  findings=4

generic:skill:pdf-optimizer@<hash12>  malicious → block  (band_default)

  [critical] harness_forgery/control-tag-close      SKILL.md:16:1  [hidden]
             Close of a trusted harness wrapper — the breakout primitive
             evidence: </system-reminder>

  [    high] harness_forgery/control-tag-open       SKILL.md:17:1  [hidden]
             Open of a reminder-role control tag with an imperative body
             evidence: <system-reminder priority="critical">

  [    high] harness_forgery/prose-terminator       SKILL.md:22:1  [hidden]
             Prose-form function-end marker (semantic escape)
             evidence: [Function results halted by user]

  [critical] prompt_injection/instruction-override  SKILL.md:27:1
             Instruction-override language
             evidence: Ignore all previous instructions
```

`--format json` and `--format sarif` are the stable machine interfaces.
JSON/SARIF are byte-identical across runs (modulo `stats.duration_ms`).

## Bands and decisions

**Band is a fact about the component. Decision is what the org does.** No exception,
pin, baseline, or vendor trust rewrites a band.

| Band (fact) | Default decision (policy) |
|---|---|
| `safe` | `allow` |
| `potential_risk` | `allow_with_notice` |
| `unsafe` | `alert` |
| `malicious` | `block` |

Four bands, four decisions. The three enforcement outcomes people quote
(allow / alert / block) are the same table: `allow_with_notice` is the fourth decision —
allow, but surface the risk. Capability-only findings (permissions, unpinned-only
supply-chain, metadata) cap the band at `potential_risk` unless an intent-bearing
finding corroborates them. Details: [Policy](docs/policy.md).

## Why bezoar

Enterprises deploy coding agents with no seatbelt. Skills and MCPs are executable
supply-chain artifacts that load straight into an agent's context. Public scanners
catch generic jailbreak phrases and leaked keys; they miss the class that actually
steers the model — **harness-envelope forgery** (content impersonating the host
harness's own control syntax). bezoar maps findings to the
[OWASP Agentic Skills Top 10](https://owasp.org/www-project-agentic-skills-top-10/)
and adds that missing family. See [Harness-envelope forgery](docs/harness-forgery.md).

A default scan performs **no network requests** and **never executes** scanned
artifacts. Same inputs → same JSON/SARIF.

## Comparison

Public posture as of 2026-09. “Harness-envelope forgery” means a first-class,
per-platform vocabulary of harness control syntax plus structural-sequence
matchers — not generic “ignore previous instructions” or “system message”
regexes.

| | Offline | Deterministic | Harness-forgery detection | Policy-as-code | SARIF | Price |
|---|---|---|---|---|---|---|
| **bezoar** | Yes (default) | Yes | Yes | Yes (`.bezoar.yml`) | Yes | Apache-2.0 |
| [Snyk agent-scan](https://github.com/snyk/agent-scan) | No (`SNYK_TOKEN` + hosted analysis) | No | No | No (Snyk org / API) | No (JSON) | Free Snyk account required; commercial platform |
| [Cisco skill-scanner](https://github.com/cisco-ai-defense/skill-scanner) | Partial (patterns/YARA offline; LLM optional) | Partial (LLM judge optional) | No | Yes (YAML presets) | Yes | Apache-2.0 |
| [invariantlabs mcp-scan](https://github.com/invariantlabs-ai/mcp-scan) | No (Invariant API or OpenAI; connects to MCP servers) | No | No | Partial (proxy guardrails) | No | Apache-2.0 + cloud / OpenAI |
| [AIR](https://air.security) | No (inline firewall + cloud) | No | No | Yes (product policy) | Not documented | Commercial |

`invariantlabs-ai/mcp-scan` now redirects into Snyk agent-scan; the column reflects
the historical Invariant CLI that still appears in comparisons. AIR is the commercial
category reference — see [FAQ](docs/faq.md#how-does-bezoar-relate-to-air).

## Docs

| Doc | What it covers |
|---|---|
| [Getting started](docs/getting-started.md) | Install, first scan, reading findings, exit codes |
| [Policy](docs/policy.md) | `.bezoar.yml`, bands vs decisions, exceptions, vendor trust |
| [Rules authoring](docs/rules-authoring.md) | Rule-pack schema, severity / disposition / confidence, `bezoar rules lint` |
| [CI](docs/ci.md) | GitHub Action, baselines, SARIF → Code Scanning |
| [Web console](docs/console.md) | Static JSON viewer and `bezoar serve` |
| [Harness-envelope forgery](docs/harness-forgery.md) | The differentiator, per-platform vocabularies, tuning |
| [Platforms](docs/platforms.md) | Discovery matrix and per-platform limits |
| [FAQ](docs/faq.md) | Offline, determinism, LLM judge, AIR, license |

## Web console

The console **never computes security facts** — it renders `bezoar.scan/v1` JSON
from the CLI.

- **Viewer:** open the static export (`console/apps/web/out/index.html` or
  `bezoar_console.dist_path()` after `pip install bezoar-console`). Drag-drop a
  JSON report; it stays in IndexedDB. SARIF is rejected — re-scan with
  `--format json`.
- **Serve:** `bezoar serve` (loopback + `/api/v1`). Needs the console extra:

```bash
pip install 'bezoar[console]'   # bezoar-console==1.0.0a2
bezoar serve
```

Details: [docs/console.md](docs/console.md).

## GitHub Action

```yaml
# permissions: { contents: read, security-events: write }
- uses: IsmailKharoub/bezoar@v1.0.0a2
  with:
    path: .
    fail-on: malicious
    upload-sarif: true
```

The Action always writes `bezoar.sarif` from `bezoar scan --format sarif`
(stdout; the CLI has no `--output`), uploads it to Code Scanning when
`upload-sarif` is true — including after a failed gate — then re-raises the
CLI exit code.

## License

Apache-2.0. Rule-pack pattern attributions (gitleaks) are in [`NOTICE`](NOTICE)
and [`src/bezoar/data/NOTICE`](src/bezoar/data/NOTICE).
