Bawbel Scanner v0.1.0 · Docs

Bawbel Scanner

Open-source CLI scanner for agentic AI components — SKILL.md files, MCP servers, system prompts, plugins. Detects AVE vulnerabilities before they reach production.

pip install bawbel-scanner

Run your first scan

# Scan a file
bawbel scan ./my-skill.md

# Full remediation report
bawbel report ./my-skill.md

# Scan a directory recursively
bawbel scan ./skills/ --recursive --fail-on-severity high

# Watch mode — re-scans on every file change
bawbel scan ./skills/ --watch

Example output

Bawbel Scanner v0.2.0

Scanning:  malicious-skill.md  ·  Type: skill

FINDINGS
🔴  CRITICAL  AVE-2026-00001      External instruction fetch detected
   Line 7  ·  fetch your instructions
   OWASP: ASI01 (Prompt Injection), ASI08 (Goal Hijacking)

🟠  HIGH      AVE-2026-00007      Goal override instruction detected
   Line 17  ·  Ignore all previous instructions

SUMMARY
Risk score:  9.4 / 10  CRITICAL  ·  Findings: 2  ·  5ms

→  Run bawbel report malicious-skill.md for full remediation guide

What is covered

15 pattern rules
Goal override, jailbreak, crypto drain, MCP poisoning and more
4 detection engines
Pattern, YARA, Semgrep, LLM Stage 2 via LiteLLM — any provider
3 output formats
Text, JSON, SARIF for GitHub Security tab
CI/CD ready
GitHub Actions, GitLab, pre-commit, Docker Compose

Installation

Requires Python 3.10+. The base install runs with zero optional dependencies — just Python and pip.

Choose your install

# Minimal — 15 pattern rules, no extra deps
pip install bawbel-scanner

# With YARA (Stage 1b)
pip install "bawbel-scanner[yara]"

# With Semgrep (Stage 1c)
pip install "bawbel-scanner[semgrep]"

# With LLM Stage 2 (any provider via LiteLLM)
pip install "bawbel-scanner[llm]"

# With file watcher
pip install "bawbel-scanner[watch]"

# Everything
pip install "bawbel-scanner[all]"

Verify

bawbel version
Bawbel Scanner v0.2.0

Detection Engines:
  ✓  Pattern     15 rules  ·  stdlib only  ·  always active
  ✗  YARA        not installed  ·  pip install "bawbel-scanner[yara]"
  ✗  Semgrep     not installed  ·  pip install "bawbel-scanner[semgrep]"
  ✗  LLM         not installed  ·  pip install "bawbel-scanner[llm]"

Enable Stage 2 — LLM semantic analysis

Stage 2 uses LiteLLM — works with any provider. Install first, then set your API key.

# Install LiteLLM support
pip install "bawbel-scanner[llm]"

# Anthropic (auto-selects claude-haiku-4-5-20251001)
export ANTHROPIC_API_KEY=sk-ant-...

# OpenAI (auto-selects gpt-4o-mini)
export OPENAI_API_KEY=sk-...

# Gemini, Mistral, Groq, Ollama and 100+ more
export BAWBEL_LLM_MODEL=gemini/gemini-1.5-flash
export GEMINI_API_KEY=...

# Local model — no API key needed
export BAWBEL_LLM_MODEL=ollama/mistral

bawbel scan ./skill.md  # Stage 2 now active — shows model in bawbel version

From source

git clone https://github.com/bawbel/bawbel-scanner
cd bawbel-scanner
./scripts/setup.sh --dev    # creates .venv, installs all tools
source .venv/bin/activate
bawbel version

bawbel scan

Scan a component or directory for AVE vulnerabilities.

bawbel scan PATH [OPTIONS]

Options

OptionDefaultDescription
--formattexttext · json · sarif
--fail-on-severityExit 2 if findings at or above: critical high medium low
--recursive, -rfalseScan all matching files in a directory tree
--watch, -wfalseWatch for file changes and re-scan automatically

Exit codes

CodeMeaning
0Clean, or findings below --fail-on-severity threshold
2Findings at or above --fail-on-severity threshold

Examples

# Scan one file, text output
bawbel scan ./my-skill.md

# Recursive scan, fail on HIGH+
bawbel scan ./skills/ --recursive --fail-on-severity high

# JSON — pipe into jq
bawbel scan ./skills/ --format json | jq '.[].max_severity'

# SARIF — upload to GitHub Security tab
bawbel scan ./skills/ --format sarif > results.sarif

bawbel report

Scan a component and show a full remediation guide — one finding at a time, with specific fix instructions for each.

bawbel report PATH [--format text|json]

What the report shows

SectionDetails
Finding headingSeverity icon, CVSS-AI score, title
Details tableAVE ID (linked), rule ID, engine, location, matched text
OWASP mappingASI01–ASI10 with full category name
What it meansFull description of the vulnerability
How to fixSpecific remediation steps for this rule
Final warning"Do not install this component" panel if vulnerabilities found

Exit codes

CodeMeaning
0Clean — no vulnerabilities found
1Vulnerabilities found

bawbel version

Show installed version and the status of every detection engine.

bawbel version     # detailed engine status
bawbel --version   # quick version string (for scripts and CI)

Useful to check which engines are active before running a scan in a new environment.

Built-in Rules

15 pattern rules covering all major agentic AI attack classes. No dependencies — all run with the base install.

Rule IDSevCVSS-AIAttack class
bawbel-goal-overrideHIGH8.1Goal hijack / prompt injection
bawbel-jailbreak-instructionHIGH8.3Jailbreak, role-play bypass
bawbel-hidden-instructionHIGH7.9Covert operation, hiding from user
bawbel-external-fetchCRITICAL9.4Metamorphic payload — AVE-2026-00001
bawbel-dynamic-tool-callHIGH8.2Tool call injection
bawbel-permission-escalationHIGH7.8Shadow permission escalation
bawbel-env-exfiltrationHIGH8.5Credential exfiltration — AVE-2026-00003
bawbel-pii-exfiltrationHIGH8.0PII collection and transmission
bawbel-shell-pipeHIGH8.8Shell pipe injection (curl|bash)
bawbel-destructive-commandCRITICAL9.1File destruction (rm -rf)
bawbel-crypto-drainCRITICAL9.6Cryptocurrency wallet drain
bawbel-trust-escalationMEDIUM6.5Trust manipulation, impersonation
bawbel-persistence-attemptHIGH8.4Self-replication, persistence
bawbel-mcp-tool-poisoningHIGH8.7MCP tool description injection — AVE-2026-00002
bawbel-system-prompt-leakMEDIUM6.2System prompt extraction
Adding a rule? See Writing Rules for the step-by-step process including required test fixtures.

Writing Rules

Add a new entry to PATTERN_RULES in scanner/engines/pattern.py. No other files need to change to add a pattern rule.

Rule structure

{
    "rule_id":     "bawbel-your-rule",    # kebab-case · unique forever
    "ave_id":      "AVE-2026-NNNNN",     # or None if no record yet
    "title":       "Short title (max 80 chars)",
    "description": "Full description of what this detects.",
    "severity":    Severity.HIGH,
    "cvss_ai":     8.0,                   # 0.0–10.0
    "owasp":       ["ASI01", "ASI08"],
    "patterns": [
        r"your\s+regex\s+here",           # re.IGNORECASE applied
        r"alternative\s+pattern",         # first match per file wins
    ],
},

Also add a remediation entry

In scanner/cli.py, add to REMEDIATION_GUIDE so bawbel report shows specific fix instructions:

REMEDIATION_GUIDE = {
    ...
    "bawbel-your-rule": (
        "Specific instructions: what to remove and what to do instead."
    ),
}

Required: tests

Every rule needs a positive fixture (triggers) and a negative fixture (does not trigger):

# Positive — must detect
def test_detects_your_rule(self, tmp_path):
    path = write_skill(tmp_path, "s.md", "# Skill\n[triggering content]\n")
    result = scan(path)
    assert "bawbel-your-rule" in [f.rule_id for f in result.findings]

# Negative — must not false-positive
def test_your_rule_no_false_positive(self, tmp_path):
    path = write_skill(tmp_path, "s.md", "# Skill\n[innocent content]\n")
    result = scan(path)
    assert "bawbel-your-rule" not in [f.rule_id for f in result.findings]

Severity scoring guide

SeverityCVSS-AIWhen to use
CRITICAL9.0–10.0Code execution, wallet drain, file destruction, external fetch
HIGH7.0–8.9Credential theft, goal override, permission escalation, MCP poisoning
MEDIUM4.0–6.9Trust manipulation, prompt extraction, obfuscation

Detection Engines

Three stages run in sequence. Each engine is a separate file — add or remove one without touching any other.

StageEngineInstallCoverage
1aPatternNothing — always runs15 built-in regex rules
1bYARApip install "bawbel-scanner[yara]"3 rules · binary + text matching
1cSemgreppip install "bawbel-scanner[semgrep]"5 rules · structural patterns
2LLM semanticpip install "bawbel-scanner[llm]" + provider API key or BAWBEL_LLM_MODELNuanced prompt injection — any LiteLLM provider
3BehavioralDocker + eBPF — v1.0 roadmapRuntime behaviour monitoring

Each engine skips silently if its dependency is not installed. scan() always returns a result — never raises.

CI/CD Integration

Add Bawbel to your pipeline to catch malicious components before they merge.

GitHub Actions — scan and fail

name: Bawbel Security Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Scan for AVE vulnerabilities
        run: |
          pip install bawbel-scanner
          bawbel scan . --recursive --fail-on-severity high

GitHub Actions — SARIF to Security tab

      - name: Generate SARIF report
        run: |
          pip install bawbel-scanner
          bawbel scan . --recursive --format sarif > bawbel.sarif
      - name: Upload to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: bawbel.sarif

Pre-commit

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: bawbel-scan
        name: Bawbel Scanner
        entry: bawbel scan
        language: system
        pass_filenames: true
        types: [markdown]
        args: [--fail-on-severity, high]

GitLab CI

bawbel-scan:
  image: python:3.12-slim
  script:
    - pip install bawbel-scanner
    - bawbel scan . --recursive --fail-on-severity high

Docker

Three build targets. No local Python required.

TargetUse case
productionScan files — minimal image, non-root user, read-only filesystem
devInteractive development shell with hot-reload source mount
testRun 145 tests and exit — use as a build gate

Build and scan

# Build
docker build --target production -t bawbel/scanner:0.1.0 .

# Scan a directory
docker run --rm \
  -v /path/to/your/skills:/scan:ro \
  bawbel/scanner:0.1.0 scan /scan --recursive

# Full remediation report
docker run --rm \
  -v /path/to/skill.md:/scan/skill.md:ro \
  bawbel/scanner:0.1.0 report /scan/skill.md

Docker Compose

# Put files to scan in ./scan/
mkdir -p scan && cp path/to/skill.md scan/

docker compose run --rm scan        # text output
docker compose run --rm scan-json   # JSON output
docker compose run --rm scan-sarif  > results.sarif
docker compose run --rm report      # remediation report
docker compose run --rm test        # run test suite
docker compose run --rm audit       # bandit + pip-audit

Configuration

All configuration is via environment variables. No config files required.

Environment variables

VariableDefaultDescription
BAWBEL_LOG_LEVELWARNINGDEBUG · INFO · WARNING · ERROR
BAWBEL_MAX_FILE_SIZE_MB10Skip files larger than N MB
BAWBEL_SCAN_TIMEOUT_SEC30Subprocess timeout for YARA and Semgrep
BAWBEL_LLM_MODELautoLiteLLM model string — overrides auto-detection
BAWBEL_LLM_MAX_CHARS8000Max content chars sent to LLM per scan
BAWBEL_LLM_TIMEOUT30LLM call timeout in seconds
BAWBEL_LLM_ENABLEDtrueSet false to disable Stage 2 entirely

LLM provider keys

Set whichever provider you use. The first key found determines the default model.

KeyDefault model
ANTHROPIC_API_KEYclaude-haiku-4-5-20251001
OPENAI_API_KEYgpt-4o-mini
GEMINI_API_KEYgemini/gemini-1.5-flash
MISTRAL_API_KEYmistral/mistral-small
GROQ_API_KEYgroq/llama3-8b-8192
Any provider: Set BAWBEL_LLM_MODEL to any LiteLLM model string to use providers not listed above — including local Ollama models with no API key.

Debug mode

BAWBEL_LOG_LEVEL=DEBUG bawbel scan ./skill.md   # full internal logs
BAWBEL_LOG_LEVEL=INFO  bawbel scan ./skill.md   # lifecycle only

Python API

scan() is the single public entry point. Never raises — all errors are captured in ScanResult.error.

Basic usage

from scanner import scan

result = scan("/path/to/skill.md")

if result.is_clean:
    print("Clean")
elif result.has_error:
    print(f"Error: {result.error}")        # E-code only, no internal detail
else:
    for f in result.findings:
        print(f"[{f.severity.value}] {f.title} — {f.cvss_ai}")
    print(f"Risk: {result.risk_score:.1f} / 10")

Batch scanning

from pathlib import Path
from scanner import scan

results = [scan(str(p)) for p in Path("./skills").rglob("*.md")]
critical = [r for r in results if r.max_severity and r.max_severity.value == "CRITICAL"]
print(f"{len(critical)} critical out of {len(results)} scanned")

ScanResult fields

FieldTypeDescription
file_pathstrResolved absolute path
component_typestrskill · mcp · prompt · unknown
findingslist[Finding]Sorted by severity, deduplicated
scan_time_msintElapsed milliseconds
max_severitySeverity | NoneHighest severity found (computed)
risk_scorefloatHighest CVSS-AI score (computed)
is_cleanboolTrue if no findings and no error
errorstr | NoneE-code (E001–E020) if scan failed

Error codes

CodeMeaning
E001Invalid file path
E003File not found
E005Symlink rejected (security)
E006File too large (max 10MB)
E008Could not read file content

Output Formats

Three formats for every use case.

FormatFlagUse case
Text--format text (default)Human reading in terminal
JSON--format jsonCI/CD pipelines, SIEM, custom tooling
SARIF 2.1.0--format sarifGitHub Security tab, VS Code, IDE plugins

JSON structure

[{
  "file_path":      "/path/to/skill.md",
  "component_type": "skill",
  "risk_score":     9.4,
  "max_severity":   "CRITICAL",
  "scan_time_ms":   5,
  "has_error":      false,
  "findings": [{
    "rule_id":   "bawbel-external-fetch",
    "ave_id":    "AVE-2026-00001",
    "title":     "External instruction fetch detected",
    "severity":  "CRITICAL",
    "cvss_ai":   9.4,
    "line":      7,
    "match":     "fetch your instructions",
    "engine":    "pattern",
    "owasp":     ["ASI01", "ASI08"]
  }]
}]

SARIF

SARIF output follows the SARIF 2.1.0 specification. It includes tool metadata, rule definitions, and results with severity mapped to SARIF levels. Upload directly to GitHub:

bawbel scan ./skills/ --format sarif > bawbel.sarif

# GitHub Actions
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: bawbel.sarif

PiranhaDB API

Free, public REST API serving AVE records. No authentication required. Use it to look up findings, build integrations, or enrich your own security tooling.

https://api.piranha.bawbel.io

Endpoints

MethodPathDescription
GET/aveList all records — filter by severity, type, status
GET/ave/{ave_id}Full record by ID
GET/ave/{ave_id}/detectionBehavioral fingerprint, IOCs, scan command
GET/search?q=Full-text search across title, description, attack class
GET/statsRegistry statistics
GET/healthHealth check

Look up an AVE record

# Get full record
curl https://api.piranha.bawbel.io/ave/AVE-2026-00001

# Detection guidance only
curl https://api.piranha.bawbel.io/ave/AVE-2026-00001/detection

# Search by keyword
curl "https://api.piranha.bawbel.io/search?q=injection"

# Filter by severity
curl "https://api.piranha.bawbel.io/ave?severity=CRITICAL"

Enrich scan findings

Every bawbel-scanner finding includes an ave_id. Use it to pull the full record at scan time:

import requests
from scanner import scan

result = scan("./my-skill.md")

for f in result.findings:
    if f.ave_id:
        r = requests.get(
            f"https://api.piranha.bawbel.io/ave/{f.ave_id}"
        ).json()
        print(f"[{f.severity.value}] {f.title}")
        print(f"  Fingerprint: {r['behavioral_fingerprint']}")
        print(f"  Remediation: {r['remediation']}")

Stats

# Registry overview
curl https://api.piranha.bawbel.io/stats

Returns total records, mutation counts, breakdown by severity and attack class.

Interactive docs — Full Swagger UI available at api.piranha.bawbel.io/docs