Skip to content

Python API

Everything the CLI does is available as a library. The design rule throughout: the rule engine is injected, never constructed inside a scanner, so you can supply your own rule packs, a subset for testing, or a fake.

Scan a model

from airlock.rules import RuleEngine, load_rules
from airlock.scanners.model import ModelScanner
from bulwark_core.severity import Severity

scanner = ModelScanner(RuleEngine(load_rules()))
result = scanner.scan("hf:org/name@revision")

print(result.worst().value, len(result.findings))
for f in result.sorted_findings():
    print(f.severity.value, f.category, f.id, f.location.path)

raise SystemExit(result.exit_code(Severity.HIGH))   # the CI contract

strict=True enables Fickling-style allowlist mode (higher recall, calibrated for the HuggingFace ecosystem):

ModelScanner(RuleEngine(load_rules()), strict=True)

Scan an MCP server or tool-spec

from airlock.scanners.mcp import MCPScanner
from airlock.scanners.toolspec.loader import load_toolspec

engine = RuleEngine(load_rules())

MCPScanner(engine).scan("python server.py")          # live (spawns the server)
MCPScanner(engine, connector=lambda _t: load_toolspec(Path("tools.json"))).scan("tools.json")

stdio scanning spawns the server

Enumerating a stdio MCP server requires starting it — the protocol offers no other way to list tools. Airlock never invokes a tool, but the server's own startup code runs. Use scan toolspec in CI, or scan in a container.

The connector is a Callable[[str], MCPInventory], which is also how tests supply a hand-built inventory with no subprocess.

Audit an agent

from warden.rules import RuleEngine, load_rules
from warden.scanner import WardenScanner
from warden.spec.model import AgentSpec, Gate, Tool

spec = AgentSpec(name="bot", autonomy="autonomous", tools=[
    Tool(name="read_file",    description="Read a file from disk", scopes=["/**"]),
    Tool(name="post_webhook", description="POST data to a URL", gate=Gate.APPROVAL),
])

result = WardenScanner(RuleEngine(load_rules())).audit_spec(spec)
print(result.score)          # 0–100 agency score

audit_spec takes an in-memory spec, so you can score a design before writing any config — useful in tests and design review.

Least-privilege recommendation

from warden.recommend.least_privilege import recommend

rec = recommend(spec)
print(rec.diff_text())
rec.changes      # applied: gates, sandboxes, allow-list placeholders, limits
rec.advisories   # need a human: breaking a toxic pair, restricting egress

The input spec is never mutated — recommend works on a deep copy.

Generate an AI-BOM

from manifest.rules import RuleEngine, load_rules
from manifest.scanner import ManifestScanner
from manifest.bom.model import AIBOM
from manifest.bom.cyclonedx import to_cyclonedx

result = ManifestScanner(
    RuleEngine(load_rules()), offline=True, scan_risk=True, govern=True
).scan("./project")

bom = AIBOM.model_validate(result.meta["aibom"])
to_cyclonedx(bom)                      # CycloneDX 1.5 dict
result.meta["governance"]              # NIST AI RMF + EU AI Act status
result.meta["risk_register"]           # component → risk → severity → action

Custom rule packs

from pathlib import Path
from airlock.rules import RuleEngine, load_rules

engine = RuleEngine(load_rules(extra_roots=[Path("./my-rules")]))

extra_roots appends to the packaged + user roots. A duplicate rule id across roots is a hard error, so a local pack cannot silently disable a built-in detection.

Render a report

from bulwark_core.report import render_report

render_report(result, "json")     # also: sarif, html
render_report(result, "terminal") # prints; returns ""

Logging

Silent by default; a library never configures logging for its host.

from bulwark_core.logging import configure
configure(verbosity=1)   # 0 WARNING · 1 INFO · 2 DEBUG — always to stderr

Diagnostics go to stderr so stdout stays valid JSON/SARIF when piped.

Core types

Type Module Purpose
Finding bulwark_core.findings id, category, severity, confidence, location, evidence, rationale, remediation, references, source
ScanResult bulwark_core.findings findings + stats + score + meta; worst(), exit_code(), sorted_findings()
Severity bulwark_core.severity ordered StrEnum: INFO < LOW < MEDIUM < HIGH < CRITICAL
finding_key / dedupe bulwark_core.findings canonical finding identity — also drives baselines and SARIF fingerprints
AgentSpec warden.spec.model the normalized agent IR every importer produces
AIBOM manifest.bom.model the inventory IR

See examples/ for runnable versions of all of the above.