Skip to main content

AI-powered attack surface mapping for red teams.

Project description

matadore

matadore

PyPI version

AI-powered attack surface mapping for red teams.

Matadore thinks like an attacker. Point it at your infrastructure - domains, IPs, repos, cloud configs, registries - and it maps your exposed surface, chains vulnerabilities into realistic kill chains, and tells you exactly how an adversary would get in.

Built for red teams and security engineers who want adversarial reasoning, not just a checklist.

High-level architecture

pip install matadore

Responsible use

Disclaimer: Matadore does not verify that you are allowed to scan any target. Use it only against systems and data you own or have explicit written authorization to test (for example a signed scope of work, pentest letter, or internal red-team charter). Unauthorized access to computer systems and networks is illegal in many jurisdictions and may result in civil liability. You are solely responsible for your use of this software and for complying with applicable laws and your organization's policies.


The Problem

Security scanners give you a list. Matadore gives you a story.

Without Matadore With Matadore
List of CVEs Chained attack paths
Scanner output Adversarial reasoning
Generic risk score "Here's how they'd get in"
Manual red team prep AI-assisted threat modelling
One-shot reports Continuous diff scanning
Standalone tool Native CI/CD integration

Quick Start

Matadore uses LiteLLM for inference - bring your own key for any supported provider. Your data never touches Matadore's servers.

from matadore import Matadore

# OpenAI
m = Matadore(
    model="gpt-4o",
    api_key="sk-...",                            # or set OPENAI_API_KEY in env
)

# Anthropic
m = Matadore(
    model="claude-opus-4-5",
    api_key="sk-ant-...",                        # or set ANTHROPIC_API_KEY in env
)

# Local model - zero data egress, no API key needed
m = Matadore(
    model="ollama/llama3",
)

report = m.engage("mydomain.com")
print(report.summary())

See Responsible use.


Supported Models

Any model string that LiteLLM recognises works out of the box. The provider is inferred automatically from the prefix.

Provider Example model string API key env var
OpenAI gpt-4o, gpt-4o-mini OPENAI_API_KEY
Anthropic claude-opus-4-5, claude-sonnet-4-5 ANTHROPIC_API_KEY
Google Gemini gemini/gemini-2.0-flash GEMINI_API_KEY
Mistral mistral/mistral-large-latest MISTRAL_API_KEY
AWS Bedrock bedrock/anthropic.claude-3-5-sonnet AWS credentials
Azure OpenAI azure/gpt-4o + base_url= AZURE_API_KEY
Ollama (local) ollama/llama3, ollama/mistral (none)

Pass the key as api_key= or export the environment variable - Matadore never requires you to manage credentials on its behalf.


Attack Surface Inputs

# Domains and subdomains
m.engage("mydomain.com",        type="domain")

# Network ranges
m.engage("192.168.1.0/24",      type="network")

# Source code and commit history
m.engage("myorg/myrepo",        type="repo")

# Entire GitHub org - public repos, secrets in history, exposed keys
m.engage("myorg",               type="github_org")

# Cloud configuration drift and IAM over-permissioning
m.engage("my-aws-account",      type="cloud", provider="aws")
m.engage("my-gcp-project",      type="cloud", provider="gcp")

# Docker registry - images with embedded credentials
m.engage("myorg/myimage",       type="docker_registry")

Core Usage

Dry-Run First

Before any active scanning, see exactly what Matadore will touch:

plan = m.engage("mydomain.com", dry_run=True)
plan.show()
# Output:
# [DOMAIN]   Will resolve and enumerate: mydomain.com + 14 discovered subdomains
# [NETWORK]  Will probe: 192.168.1.12, 192.168.1.45 (skipping .0/broadcast)
# [REPO]     Will clone (read-only): myorg/myrepo @ HEAD
# [CLOUD]    Will call: ec2:DescribeInstances, s3:ListBuckets (read-only IAM)
#
# Total assets in scope: 31
# Estimated duration: ~4 min (active mode)
# Run m.engage("mydomain.com") to proceed.

dry_run=True prevents accidental self-DoS, IDS triggering, or scanning assets outside your authorized scope.

Map the Attack Surface

report = m.engage("mydomain.com")

report.surface                     # all exposed assets
report.vulnerabilities             # findings ranked by exploitability
report.entry_points()              # most likely initial access vectors
report.shadow_it()                 # assets you didn't know were public

The shadow_it() view is often the most valuable output - surfacing dev servers, staging environments, and forgotten S3 buckets that your team didn't know were exposed.

Chain Vulnerabilities into Attack Paths

# Full adversarial kill chain
report.attack_paths()

# What gets hit first, and why
report.what_would_they_hit_first()

Every finding includes an evidence field - the exact port response, code line, or API response that substantiates the claim. Matadore never asserts a path it cannot reference.

for path in report.attack_paths():
    print(path.narrative)   # human-readable kill chain
    print(path.evidence)    # source data: line of code, packet capture, API response
    print(path.mitre_ttps)  # mapped ATT&CK techniques

Live Streaming Mode

# Findings stream in real-time as recon runs
for event in m.engage("mydomain.com", stream=True):
    print(f"[{event.level}] {event.description}")
    # [CRITICAL] Exposed .env file at https://api.mydomain.com/.env
    # [HIGH]     SSH key found in commit a3f91c (myorg/infra-repo)
    # [INFO]     Subdomain discovered: staging.mydomain.com

    # Stop if a sensitive asset appears
    if "prod-db" in event.asset:
        event.halt()

Generate a Red Team Brief

brief = report.brief()
brief.to_markdown()
brief.to_pdf()

Example output:

"An attacker would likely begin with the exposed Jenkins instance (CVE-2024-23897), extract the AWS access key embedded in build logs, assume an over-permissioned IAM role, pivot to S3, and exfiltrate the customer database - estimated time to impact: under 4 hours."

Diff Mode - Track What Changed

report = m.engage("mydomain.com").diff(since="last_week")
report = m.engage("mydomain.com").diff(since="2026-04-01")
report = m.engage("mydomain.com").diff(baseline=previous_report)

Matadore caches engagement state locally (SQLite) so repeated scans only re-process assets that have changed. Large infrastructure scans don't start from zero.


CLI

Red teams live in the terminal. The CLI uses Rich for colour-coded output, live progress bars, and interactive tables.

# Full engagement
matadore engage mydomain.com

# Dry-run preview
matadore engage mydomain.com --dry-run

# Live stream findings
matadore engage mydomain.com --stream

# Diff since last scan
matadore engage mydomain.com --diff

# Export report
matadore engage mydomain.com --export sarif --out results.sarif
matadore engage mydomain.com --export pdf   --out brief.pdf

The web UI (if running) is a read-only management dashboard - for sharing findings with stakeholders. All actions and scans live in the SDK and CLI.


CI/CD Integration

report = m.engage(os.environ["DEPLOY_DOMAIN"])

# Fail the pipeline on new critical findings
report.assert_no_new_criticals()

# SARIF → GitHub Security tab
report.export("sarif", path="matadore-results.sarif")

# JSON-ASFF → AWS Security Hub / Jira / Splunk
report.export("asff", path="matadore-results.asff.json")

GitHub Actions example:

- name: Matadore attack surface scan
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}   # or ANTHROPIC_API_KEY, etc.
  run: |
    python -c "
    from matadore import Matadore
    m = Matadore(model='gpt-4o')
    report = m.engage('${{ vars.DEPLOY_DOMAIN }}')
    report.export('sarif', path='results.sarif')
    report.assert_no_new_criticals()
    "

- name: Upload to GitHub Security
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif

Report Object

report.surface                     # all exposed assets
report.vulnerabilities             # findings ranked by exploitability, with evidence
report.attack_paths()              # chained exploit sequences
report.entry_points()              # most likely initial access vectors
report.shadow_it()                 # assets not in your known inventory
report.what_would_they_hit_first() # adversary priority model
report.brief()                     # narrative red team report
report.mitre_mapping()             # findings mapped to ATT&CK tactics/techniques
report.remediation_plan()          # prioritised fix list with effort estimates
report.delta(previous_report)      # regression detection across engagements
report.audit_log()                 # tamper-evident chain of custody
report.assert_no_new_criticals()   # CI/CD gate
report.export("sarif")             # SARIF - GitHub Advanced Security
report.export("asff")              # JSON-ASFF - AWS Security Hub, Splunk, Jira
report.export("markdown")          # red team brief
report.export("pdf")               # stakeholder report

Every Vulnerability and AttackPath object is a typed Pydantic model with a mandatory evidence field. The AI cannot assert a finding without a verifiable source reference.


Plugin Architecture

Matadore separates orchestration from scanning:

  • Core = The Brain - AI reasoning, path chaining, report generation
  • Plugins = The Eyes - wrappers around existing best-in-class tools
# Built-in plugins
from matadore.plugins import Nmap, Nuclei, Trivy, GitLeaks, ScoutSuite

m = Matadore(
    model="gpt-4o",
    plugins=[Nmap(), Nuclei(), Trivy(), GitLeaks()],
)

Write your own:

from matadore.plugins import BasePlugin

class MyScanner(BasePlugin):
    def run(self, target: str) -> list[RawFinding]:
        ...

This means Matadore doesn't re-implement what nmap, nuclei, and trivy already do well - it reasons over their output.


Scan Modes

# Passive - no active probing, zero noise (default for CI)
m.engage("mydomain.com", mode="passive")

# Active - full fingerprinting and exploit chaining (default for red team)
m.engage("mydomain.com", mode="active")

# Stealth - mimics normal traffic patterns, rate-limited
m.engage("mydomain.com", mode="stealth")

All modes include automatic rate-limiting with smart back-off - Matadore self-throttles against GitHub APIs, cloud endpoints, and rate-limited services to avoid triggering alarms or burning API quotas.


Audit trail

Engagements are designed to support an audit log - a record of what was scanned, when, and by whom - so you can align with your own governance (documented pentest, red-team program, or internal policy). Matadore does not replace legal review or customer authorization workflows.

report.audit_log()  # chain of custody for this engagement

Deployment

Matadore supports two deployment models:

Mode How it works Best for
Cloud provider key You supply an OpenAI / Anthropic / Gemini key; inference runs directly with that provider Startups, small red teams, quick onboarding
On-Premise (Ollama) Run a local model inside your network; zero data egress Enterprise, regulated industries, classified environments
Azure / Bedrock Use your existing cloud LLM endpoint; data stays in your VPC Enterprises with existing cloud LLM contracts
# On-premise - local Ollama model, no data leaves your network
docker run --rm \
  -e MATADORE_MODEL=ollama/llama3 \
  -e OLLAMA_BASE_URL=http://your-ollama-host:11434 \
  -v $(pwd)/reports:/reports \
  matadore/runner:latest \
  engage mydomain.com --export sarif --out /reports/results.sarif

Technical Design

Concern Approach
LLM inference LiteLLM - single interface over OpenAI, Anthropic, Gemini, Bedrock, Ollama, and more
Type safety Pydantic models for all Finding, AttackPath, and Report objects
Concurrency asyncio throughout the mapping phase - 100 IPs scan in parallel, not sequence
State & caching SQLite (local) or DuckDB (team-shared) to avoid re-scanning unchanged assets
Rate limiting Automatic back-off on GitHub, AWS, and GCP API endpoints
Testing VCR.py records and replays network interactions - CI runs never consume API credits
Evidence requirement Every Finding has a mandatory evidence: Evidence field - no unsubstantiated assertions

Domain Model

Matadore domain model


Roadmap

  • type="slack_workspace" - social engineering surface mapping
  • type="email_domain" - phishing and pretexting exposure
  • Azure provider support
  • Attack graph visualization (Cytoscape.js) in the web dashboard
  • VS Code extension - inline findings during development
  • Team mode - shared org state and collaboration across engagements
  • Engagement templates (external pentest, insider threat, supply chain)

Install

pip install matadore

Requires Python 3.10+.


Documentation

Documentation is built with Zensical and deployed to GitHub Pages.

API documentation is auto-generated from docstrings using mkdocstrings.

Docs deploy automatically on push to main via GitHub Actions. To enable this, go to your repo's Settings > Pages and set the source to GitHub Actions.


Development

To set up for local development:

# Clone your fork
git clone git@github.com:your_username/matadore.git
cd matadore

# Install in editable mode with live updates
uv tool install --editable .

This installs the CLI globally but with live updates - any changes you make to the source code are immediately available when you run matadore.

Run tests:

uv run pytest

Run quality checks (format, lint, type check, test):

just qa

Author

matadore was created in 2026 by Dario Mory.


License

MIT - see LICENSE.

Legal notice: Matadore does not check whether you are permitted to scan a target. Only scan infrastructure you own or have explicit written authorization to test. Unauthorized scanning is illegal in most jurisdictions. See Responsible use.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

matadore-0.1.0.tar.gz (11.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

matadore-0.1.0-py3-none-any.whl (36.8 kB view details)

Uploaded Python 3

File details

Details for the file matadore-0.1.0.tar.gz.

File metadata

  • Download URL: matadore-0.1.0.tar.gz
  • Upload date:
  • Size: 11.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for matadore-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d7636ff0704f26d723bfa68e0895c358a0f2354243b3f064dfa9b503ed51aeff
MD5 ef9c9fc1076d8317cc01378e1a5f33a2
BLAKE2b-256 70422ceac46e2158380039090933d6f226aa7d099d5b82f57ae4fee0403b02ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for matadore-0.1.0.tar.gz:

Publisher: publish.yml on dariomory/matadore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file matadore-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: matadore-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 36.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for matadore-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7d7824e82b859dd97785432deb4153cd0f0274672be181eaf09f46bac046fc01
MD5 7e1dc9a3d0d1b5547a01c5092ad99cab
BLAKE2b-256 dd691f41d9106122b077db6aa25e5b4c184dfe8c311a508c331f7c3726827a60

See more details on using hashes here.

Provenance

The following attestation bundles were made for matadore-0.1.0-py3-none-any.whl:

Publisher: publish.yml on dariomory/matadore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page