This release is a pre-release and may not be stable for production use.
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.
1.0.0a4 ships the scanner plus the self-hosted console: inventory, finding
lifecycle, CI ingest, notifications, policy history, frameworks, the local
guard daemon, MCP gateway, install quarantine, and an agent-readable surface.
The commands below and the guides in docs/ describe that shipped surface.
60-second quickstart
Requires Python ≥ 3.11.
# 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 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>.
✗ 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.
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 and adds that missing family. See Harness-envelope forgery.
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 | No (SNYK_TOKEN + hosted analysis) |
No | No | No (Snyk org / API) | No (JSON) | Free Snyk account required; commercial platform |
| Cisco skill-scanner | Partial (patterns/YARA offline; LLM optional) | Partial (LLM judge optional) | No | Yes (YAML presets) | Yes | Apache-2.0 |
| invariantlabs mcp-scan | No (Invariant API or OpenAI; connects to MCP servers) | No | No | Partial (proxy guardrails) | No | Apache-2.0 + cloud / OpenAI |
| AIR | 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
| Doc | What it covers |
|---|---|
| Getting started | Install, first scan, reading findings, exit codes |
| Policy | .bezoar.yml, bands vs decisions, exceptions, vendor trust |
| Rules authoring | Rule-pack schema, severity / disposition / confidence, bezoar rules lint |
| CI | GitHub Action, baselines, SARIF, shipping reports to a console |
| CI ingest | POST /ingest envelope, scoped tokens, HMAC |
| Web console | Static JSON viewer and bezoar serve |
| Inventory and lifecycle | Assets, hosts, finding status / owner / exceptions |
| Notifications | Slack, Teams, email, webhook |
| Frameworks | OWASP / ATLAS / ATT&CK mappings and coverage |
| Guard | Desktop daemon, shims, ceilings, ship-to-serve |
| MCP gateway | stdio wrap + HTTP proxy, tool poisoning |
| Fetch / trust / quarantine | bezoar fetch, promote, trust ledger |
| Agents | llms.txt, well-known, bezoar mcp-server |
| Self-hosting | One-click deploys, compose, /data, upgrade, backup |
| Serve config | bezoar-serve.yml keys, types, env vars |
| SSO and RBAC | token / OIDC / proxy, human and machine roles |
| Audit and retention | Hash-chained JSONL, report pruning |
| Harness-envelope forgery | The differentiator, per-platform vocabularies, tuning |
| Platforms | Discovery matrix and per-platform limits |
| FAQ | Offline, determinism, guard privacy, AIR, license, SSO |
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.htmlorbezoar_console.dist_path()afterpip 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:
pip install 'bezoar[console]' # bezoar-console==1.0.0a2
bezoar serve
Details: docs/console.md.
Self-host the console — enterprise features are a config file
Everything the AI-agent security vendors charge enterprise prices for — SSO, role-based access, a tamper-evident audit log, retention — ships in bezoar as optional YAML. No SKU, no sales call, no per-seat pricing. One container, one volume, Apache-2.0.
# docker compose (any VPS)
curl -fsSLO https://raw.githubusercontent.com/IsmailKharoub/bezoar/v1.0.0a3/deploy/compose/docker-compose.yml
BEZOAR_SERVE_TOKEN="$(openssl rand -hex 32)" docker compose up -d
# → http://127.0.0.1:8080 (add the `tls` profile + BEZOAR_DOMAIN for HTTPS via Caddy)
# Fly.io
fly launch --from https://github.com/IsmailKharoub/bezoar --no-deploy
fly secrets set BEZOAR_SERVE_TOKEN="$(openssl rand -hex 32)" && fly deploy
Then turn on the "enterprise" bits by editing bezoar-serve.yml:
auth:
mode: oidc
oidc: { issuer: https://login.example.com, client_id: bezoar, client_secret_file: /run/secrets/oidc }
roles:
default: viewer
groups: { admin: [security-leads], editor: [security-eng] }
audit: { enabled: true }
retention: { reports: { max_age_days: 180 } }
| bezoar | AIR | Alice (Caterpillar) | |
|---|---|---|---|
| Self-hosted console | Yes — one container | No (cloud-only) | No |
| SSO (OIDC) | Yes, config | Enterprise contract | — |
| RBAC | Yes, config (viewer / editor / admin) | Enterprise contract | — |
| Audit log | Yes — hash-chained JSONL, bezoar audit verify |
Enterprise contract | — |
| Pricing | Apache-2.0 | Demo-gated, annual | SaaS |
Default posture is unchanged: bezoar serve still binds loopback with a random bearer token
and touches no network. Every feature above is opt-in. → Self-hosting
GitHub Action
# 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
and src/bezoar/data/NOTICE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bezoar-1.0.0a4.tar.gz.
File metadata
- Download URL: bezoar-1.0.0a4.tar.gz
- Upload date:
- Size: 682.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e70c537ca94625184a0b4eb09b7eefe75eadabf7f6bd4133beb4e9cc8a13d606
|
|
| MD5 |
885ea289f34398bec33c883a6bbd2f33
|
|
| BLAKE2b-256 |
9e7c5bb9a2824cb29de09822dbd53e5b87a2acee9956c3697add18c4dd2e2adb
|
Provenance
The following attestation bundles were made for bezoar-1.0.0a4.tar.gz:
Publisher:
release.yml on IsmailKharoub/bezoar
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bezoar-1.0.0a4.tar.gz -
Subject digest:
e70c537ca94625184a0b4eb09b7eefe75eadabf7f6bd4133beb4e9cc8a13d606 - Sigstore transparency entry: 2733941935
- Sigstore integration time:
-
Permalink:
IsmailKharoub/bezoar@04b66624106431e95fd096f91d4f0cba23c5c62b -
Branch / Tag:
refs/tags/v1.0.0a4 - Owner: https://github.com/IsmailKharoub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@04b66624106431e95fd096f91d4f0cba23c5c62b -
Trigger Event:
push
-
Statement type:
File details
Details for the file bezoar-1.0.0a4-py3-none-any.whl.
File metadata
- Download URL: bezoar-1.0.0a4-py3-none-any.whl
- Upload date:
- Size: 517.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80b6cf5a4a8072f889e335440e1788bd312e77fbf06d81dbd257f2e06e933b91
|
|
| MD5 |
dac6b8cce86e020831e910c49808a452
|
|
| BLAKE2b-256 |
0fd975ff937f9193191c61034413a418a35a918348de725e1ecfc7e70786800a
|
Provenance
The following attestation bundles were made for bezoar-1.0.0a4-py3-none-any.whl:
Publisher:
release.yml on IsmailKharoub/bezoar
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bezoar-1.0.0a4-py3-none-any.whl -
Subject digest:
80b6cf5a4a8072f889e335440e1788bd312e77fbf06d81dbd257f2e06e933b91 - Sigstore transparency entry: 2733941946
- Sigstore integration time:
-
Permalink:
IsmailKharoub/bezoar@04b66624106431e95fd096f91d4f0cba23c5c62b -
Branch / Tag:
refs/tags/v1.0.0a4 - Owner: https://github.com/IsmailKharoub
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@04b66624106431e95fd096f91d4f0cba23c5c62b -
Trigger Event:
push
-
Statement type: