Prooflint is a local-first evidence gate for AI-security findings and AI-assisted vulnerability reports. It turns “this looks vulnerable” into a deterministic checklist: what was authorized, what exact target was tested, what was expected, what was observed, which artifacts support the claim, and how another reviewer can retest it.
Prooflint does not scan targets, execute proof-of-concept payloads, prove exploitability, assign truth, certify compliance, or submit reports. It parses local Markdown or JSON and reads only the evidence files you explicitly reference.
AI-generated security reports can sound certain while omitting the details that make a claim reviewable. Prooflint checks the report structure before triage, disclosure, or publication. Its rules favor bounded claims and reproducible evidence over persuasive language.
Prooflint requires Python 3.11 or newer.
python -m pip install -e .
prooflint --versionFor contributors using uv:
uv sync --extra dev
uv run prooflint --versionCreate a report template:
prooflint init finding.mdReplace every placeholder, place safely redacted evidence beneath a dedicated directory, record its SHA-256 in the finding, then check it:
prooflint check finding.md --evidence-root ./review-bundleGenerate and verify a deterministic manifest:
prooflint manifest finding.md \
--evidence-root ./review-bundle \
--out evidence-manifest.json
prooflint verify evidence-manifest.json --evidence-root ./review-bundleMachine-readable output is available for automation:
prooflint check finding.md --format json
prooflint check finding.md --format sarif --out prooflint.sarifSee the fully synthetic passing example in
examples/findings/verified-synthetic.md and
its committed evidence-manifest.json, plus the
Turkish quick start.
Markdown findings use YAML frontmatter followed by optional reviewer notes. JSON findings
use the same fields and may include a body string. The current schema version is 0.1.
---
schema_version: "0.1"
id: FINDING-001
title: Synthetic authorization-boundary regression
profile: ai-system
status: verified
target:
system: synthetic-agent-fixture
version: fixture-v1
scope:
authorized: true
description: Local synthetic fixture only
boundaries: [tool-approval-gate]
preconditions: [A synthetic request reaches the approval gate]
affected_boundary: tool-approval-gate
expected: The request is denied before a tool action is accepted.
observed: The fixture records an accepted request after a policy label is removed.
impact: The attached redacted trace supports a hypothetical authorization-bypass claim.
confidence: high
evidence:
- kind: redacted_trace
path: evidence/trace.txt
sha256: 64-lowercase-hex-characters
state: verified
redacted: true
sensitive: false
description: Synthetic trace with no live target data
reproduction:
attempts: 3
successes: 3
environment: Offline synthetic fixture
steps: [Replay the fixed fixture and compare its recorded decision]
remediation: Bind the policy label to the signed request envelope.
retest: [Replay the same fixture and require three denied decisions]
---The authoritative machine-readable schemas ship in
src/prooflint/schema.
| Rule | Requirement |
|---|---|
PL001 |
Target identifier and immutable fingerprint are present. |
PL002 |
Explicit authorization and scope are recorded. |
PL003 |
Preconditions and the affected boundary are named. |
PL004 |
verified claims have successful attempts and verified evidence. |
PL005 |
Expected and observed behavior are both present and distinct. |
PL006 |
Impact is stated and linked to an evidence file. |
PL007 |
Reproduction counters, environment, steps, and status are consistent. |
PL008 |
Evidence paths are safe and declared SHA-256 hashes match. |
PL009 |
Remediation and deterministic retest steps are present. |
PL010 |
Sensitive evidence is marked redacted. |
Rules are deterministic and run in rule-code order. A rule violation is evidence hygiene feedback—not a verdict that the underlying security claim is false.
| Code | Meaning |
|---|---|
0 |
All findings passed, or the manifest verified. |
1 |
One or more rule violations or verification mismatches were found. |
2 |
Usage, finding/manifest parsing, schema-version, or tool error. |
from prooflint import build_manifest, load_finding, validate_finding, verify_manifest
finding = load_finding("finding.md")
result = validate_finding(finding, evidence_root="review-bundle")
if result.passed:
manifest = build_manifest(finding, evidence_root="review-bundle")Parser and validator results are deeply immutable, including values created through the
exported dataclass constructors. build_manifest intentionally returns a fresh plain mapping
for direct JSON serialization; callers may mutate that owned copy without changing the finding
or a later manifest. The API never contacts a network service.
- Finding and manifest files must be regular UTF-8 files no larger than 2 MiB.
- Duplicate YAML/JSON keys and non-standard JSON constants are rejected as ambiguous input.
- Symlinks are rejected at the finding, manifest, output, and evidence leaves and at every existing ancestor component.
- Evidence paths must be canonical POSIX-relative paths beneath the selected root.
- Absolute paths,
.., backslashes, duplicate entries, and non-regular files are rejected. - SHA-256 is streamed from the same no-follow descriptor that was confined and checked; manifests contain no timestamp or absolute path.
- File reporters write atomically and refuse to overwrite an input finding or evidence file.
- A JSON report written inside a scanned directory becomes a discovered input on the next run; choose an output outside that directory instead of asking Prooflint to overwrite it.
- Prooflint never renders HTML, evaluates templates, imports finding code, or executes a command from a report.
- Keep credentials, personal data, proprietary prompts, live exploit payloads, and raw customer data out of review bundles. Redact before hashing.
Path confinement reduces accidental disclosure and traversal risk, but it does not make untrusted content safe to publish. Review artifacts manually before sharing them.
uv sync --extra dev --locked
uv run ruff format --check .
uv run ruff check .
uv run mypy src
uv run pytest --cov=prooflint --cov-report=term-missing --cov-fail-under=95
uv build --no-build-isolation --clear
uv run twine check dist/*
uv run check-wheel-contents dist/*.whlhatchling==1.27.0 is pinned both as the PEP 517 backend and in uv.lock. The
--no-build-isolation build above therefore consumes the backend and dependencies installed by
the locked sync. Build twice from the same source into empty output directories and compare both
artifacts byte-for-byte when checking reproducibility.
See CONTRIBUTING.md for test and change requirements and SECURITY.md for responsible vulnerability reporting.
Apache License 2.0. See LICENSE.