Skip to main content
ROE-Lint logo

Stop out-of-scope commands before they run.

CI License: MIT Python 3.10+ SARIF

ROE-Lint turns an existing Rules of Engagement document into a local policy firewall for human operators, CI pipelines, and AI red-team agents. It blocks out-of-scope commands before any security tool is executed.

[!IMPORTANT] ROE-Lint performs static analysis only. It never scans a target, executes a playbook, or grants authorization. Written permission from the system owner remains mandatory.

Why this exists

Rules of Engagement usually live in a PDF while operations live in terminals, scripts, and agent tool calls. That gap makes simple mistakes expensive: an excluded production host gets swept, an approval expires, or a runtime variable resolves outside the client boundary.

ROE-Lint turns the relevant boundaries into reviewable policy-as-code:

  • Local PDF, DOCX, TXT, and Markdown ROE import with no API key
  • Source evidence, confidence scores, and an explicit human approval gate
  • CIDR, IP, exact-domain, and wildcard-domain scope checks
  • Exclusions that always override inclusions
  • Authorization expiry checks
  • Tool and technique allow/deny policy
  • Approval gates for sensitive techniques
  • Detection of unresolved shell targets, plaintext secrets, and destructive operations
  • Human-readable, JSON, and SARIF output
  • A reusable GitHub Action for pull-request gates
  • An optional MCP server for AI agents

30-second demo

pipx install roelint

# Convert the client's existing ROE document into a cited, reviewable draft.
roelint import-roe client-roe.pdf -o roe.draft.yml --report roe.review.json

# Review the draft and evidence, then explicitly approve it.
roelint approve-policy roe.draft.yml -o roe.yml --reviewed-by "Analyst Name"

# Check one proposed command. It is inspected, never executed.
roelint check-command --policy roe.yml -- nmap -sV 10.20.10.50
# ERROR ROE002 [command] target '10.20.10.50' is explicitly excluded
#       help: Source: client-roe.pdf, page 4, line 18 — "Payment systems must not be tested."

To try the repository version:

python -m pip install -e ".[dev]"
pytest

Or run the zero-persistence CLI container with the current directory mounted read-only:

docker build -t roelint .
docker run --rm -v "$PWD:/workspace:ro" roelint \
  check examples/playbook.safe.yml -p examples/roe.yml

Import an existing ROE document

You do not have to write YAML from scratch. Give ROE-Lint the customer's existing text-based PDF, DOCX, TXT, or Markdown file:

roelint import-roe ACME-Rules-of-Engagement.pdf \
  --output roe.draft.yml \
  --report roe.review.json

The importer detects:

  • in-scope and excluded IPs, CIDRs, URLs, exact domains, and wildcard domains;
  • authorization owner and expiry dates;
  • permitted tools;
  • prohibited techniques and techniques requiring prior approval;
  • common English and Turkish ROE wording.

roe.draft.yml contains the extracted policy. roe.review.json records each decision's source page, line, excerpt, and confidence score. Targets without clear “in scope” or “out of scope” context remain under scope.unresolved; they are never silently authorized.

Imported policies are deliberately marked review.status: draft. ROE-Lint refuses to use a draft for operational checks. After comparing it with the evidence report:

roelint approve-policy roe.draft.yml \
  --output roe.yml \
  --reviewed-by "Analyst Name"

If owner or expiry wording is unusual, provide those values without editing YAML:

roelint import-roe client.pdf \
  --owner security@client.example \
  --expires 2026-12-31

Scanned/image-only PDFs need OCR before import. Complex or contradictory legal language remains a human review task; ROE-Lint automates transcription and evidence gathering, not authorization.

Check a command

Use check-command in a terminal, shell integration, or agent approval hook. Everything after -- is treated as data and statically inspected; ROE-Lint never runs it.

roelint check-command -p roe.yml -- nmap -sV 10.20.4.10
# ROE-Lint: PASS — no policy violations found

roelint check-command -p roe.yml -- nmap -sV 10.20.10.50
# ERROR ROE002 [command] target '10.20.10.50' is explicitly excluded

Exit code 0 means allow, 1 means block, and 2 means the input or policy is invalid. Add --format json for machine-readable decisions.

AI agent policy firewall (MCP)

Install the optional MCP integration:

pipx install "roelint[agent]"

Add the local stdio server to any MCP-compatible host:

{
  "mcpServers": {
    "roelint": {
      "command": "roelint-mcp",
      "args": ["--policy", "/absolute/path/to/roe.yml"]
    }
  }
}

The server exposes one deliberately narrow tool: authorize_command(command, targets?). It returns allow or block, findings, engagement ID, and executed: false. Agent instructions should require this tool before every security command and treat a block decision as final.

Policy

version: 1

engagement:
  id: ACME-2026-08

authorization:
  owner: security@acme.example
  expires: "2099-12-31"

scope:
  include:
    - 10.20.0.0/16
    - "*.lab.acme.example"
  exclude:
    - 10.20.10.50
    - payments.lab.acme.example

rules:
  allowed_tools: [nmap, curl, nuclei]
  prohibited_techniques: [denial-of-service, data-destruction]
  approval_required: [credential-access, phishing]

approvals:
  CHANGE-4821:
    approved: true
    approver: security@acme.example

Playbook

ROE-Lint consumes a deliberately small, vendor-neutral format. Existing orchestrators can export it without giving ROE-Lint execution privileges.

name: ACME lab validation
steps:
  - id: discovery
    technique: network-service-scanning
    command: nmap -sV --max-rate 25 10.20.4.0/24
    targets: [10.20.4.0/24]

  - id: approved-validation
    technique: credential-access
    approval: CHANGE-4821
    command: nuclei -u https://api.lab.acme.example

Declare targets when a command builds its destination dynamically. ROE-Lint still emits ROE008 for shell expansion because declared intent and runtime behavior can diverge.

GitHub Actions

name: ROE policy gate
on: [pull_request]

jobs:
  roe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: r00tmancer/roelint@v1
        with:
          playbook: ops/playbook.yml
          policy: ops/roe.yml
          fail-on: warning

For GitHub Code Scanning, generate SARIF and upload it:

roelint check ops/playbook.yml -p ops/roe.yml --format sarif -o roelint.sarif

Rules

ID Default What it catches
ROE001 Error Invalid or expired authorization
ROE002 Error Excluded or out-of-scope target
ROE003 Error Tool missing from the reviewed allow-list
ROE004 Error Prohibited technique
ROE005 Error Missing or invalid approval
ROE006 Error Potentially destructive operation
ROE007 Error Possible plaintext credential or token
ROE008 Warning Missing or dynamically constructed target
ROE009 Error Command that cannot be parsed

Design boundaries

  • No execution: static input in, findings out.
  • Exclusion wins: a broad include can never override a narrow exclusion.
  • No DNS resolution: lint results do not depend on mutable network state.
  • No implied authorization: a passing result means the file matches the policy, not that an engagement is legally authorized.
  • Fail closed where it matters: invalid policies, expired authorization, and unknown tools produce errors.

Validation

The parser is covered by English/Turkish scope regression cases and unit tests for wildcard domains, CIDR containment, exclusion precedence, approvals, destructive operations, secret detection, document import, source citations, CLI exit codes, and agent decisions. The synthetic corpus is intentionally described as regression coverage—not a real-world extraction accuracy claim. See benchmarks/ and run:

python benchmarks/run.py

Roadmap

  • OCR for scanned ROE documents
  • Optional local/hosted LLM extraction for complex prose, with deterministic validation
  • JSON Schema editor hints and schema validation
  • Adapters for Prelude, VECTR, Atomic Red Team, and custom pipelines
  • Signed policy/approval envelopes with Sigstore
  • Optional OPA/Rego policy backend
  • Rate and blackout-window validation
  • A stable v1 playbook interchange specification

See CONTRIBUTING.md for extension points. Security issues belong in the private channel described in SECURITY.md.

License

MIT. Use it for lawful, explicitly authorized security work.

Metadata

Release files for roelint 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for roelint 0.1.0
File Size Uploaded
roelint-0.1.0.tar.gz 258.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for roelint 0.1.0
File Interpreter ABI Platform
roelint-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 278.6 kB

Release files / roelint-0.1.0.tar.gz

Download URL roelint-0.1.0.tar.gz
Size 258.0 kB
Tags Source
SHA-256 checksum
How to use checksums
48db1d05d78162e80d5f9bf2091dc5f407e06bc43bffb1bc19d332533d1940ca
BLAKE2b-256 checksum
How to use checksums
ea7ac6d3709b76f3ed807a9319ce98c31d2c6a1e88d2cc8e24f41e800eb0b57c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.

Transparency log

Release files / roelint-0.1.0-py3-none-any.whl

Download URL roelint-0.1.0-py3-none-any.whl
Size 20.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
afa094415811f7603033ee01ee2c6ecbe6fe61ec9e46e3de7a1178425c7909b6
BLAKE2b-256 checksum
How to use checksums
765267af6350ace3f9f7381572a65e2feb0026c699f2e44c81683b4ece42010b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page