Security scanner for Ansible playbooks - detects malicious code, credential exposure, offensive tooling, data exfiltration, and 500+ other security anti-patterns.
Project description
Ansible Security Scanner
A security scanner for Ansible playbooks. Detects malicious code, unauthorized cloud access, offensive tooling, reverse shells, data exfiltration, and 500+ additional security anti-patterns. Generates detailed reports with remediation guidance.
1091 rules across 31 categories -- all auto-discovered from YAML pattern plugins.
[!NOTE] Scope. This is a static, pattern-based scanner - one layer in a defense-in-depth strategy. Pair it with the runtime controls you already trust (AAP/AWX approval gates, execution-environment lockdown, network egress policy, code review) for full coverage. See Limitations for the specific classes of issue this layer cannot catch on its own.
Contents
- Installation
- Quick Start
- Project Structure
- Documentation
- Requirements
- Contributing
- Security
- License & Attribution
Built with
Installation
pip install ansible-security-scanner
Requires Python 3.11+. Installs an ansible-security-scanner command on your PATH.
Quick Start
After pip install ansible-security-scanner, try one of these:
# 1. Scan the current directory, print a Markdown report to the terminal
ansible-security-scanner
# 2. Scan a project and write a SARIF report for GitHub Code Scanning
ansible-security-scanner --directory ansible/ --output results.sarif
# 3. CI/CD: emit a GitLab SAST report (auto-populates the Security Dashboard)
ansible-security-scanner --directory ansible/ --output gl-sast-report.json
# 4. One Markdown report per scanned playbook (lands under ./security-reports/)
ansible-security-scanner --directory ansible/ --output-per-file --format markdown
# 5. Only fail the build on secrets-related findings
ansible-security-scanner --directory ansible/ --compliance CIS-Secrets
# 6. Dry-run autofix - emit a unified diff of the changes the scanner would make
ansible-security-scanner --files site.yml --fix --fix-output fixes.patch
# 7. CI/CD: post a concise findings comment on the current PR / MR
# (auto-detects GitHub Actions vs. GitLab CI; works on self-hosted).
ansible-security-scanner --gh-comment # inside a pull_request workflow
ansible-security-scanner --gl-comment # inside a merge_request_event pipeline
# Full list of flags & examples:
ansible-security-scanner --help
Smart defaults
- No
--formatgiven? If you pass--output report.sarif, the format is inferred from the extension (.sarif-> SARIF,.json-> JSON,.md/.markdown-> Markdown,.html/.htm-> HTML,.xml-> XML,.yml/.yaml-> YAML,.csv-> CSV). An explicit--formatalways wins; the scanner logs a warning if the two disagree so pipeline misconfigurations fail loudly. - No
--outputgiven with--output-per-file? Reports land under./security-reports/(a self-documenting directory; add it to.gitignore). --outputwould overwrite an input file? The scanner refuses and exits with code 2 - common footgun when running--files site.yml --output site.ymlwith.yml-as-format inference.
Working from a source checkout? Use python main.py ... instead of the
installed CLI - it's a thin shim around the same entry point.
Project Structure
ansible-security-scanner/ # repo root (hyphenated - matches PyPI)
├── pyproject.toml # packaging + pytest + hatch-vcs
├── README.md
├── CONTRIBUTING.md
├── RELEASING.md # maintainer release runbook
├── LICENSE / NOTICE # Apache-2.0 + attribution terms
├── main.py # local dev entry point (py main.py ...)
├── .github/workflows/ # CI, release, docs workflows
├── .hugo/ # Hugo documentation site
│ ├── scripts/build_docs.py # Generates Hugo content from docs/ + patterns
│ └── content/ # Generated .md pages (do not edit by hand)
├── docs/ # Long-form prose docs (source of truth)
│ ├── assets/ # Images & SVGs the README references
│ ├── cli.md # CLI Reference (and exit codes, suppressions, dedup)
│ ├── environment.md # Environment Variables
│ ├── api.md # Programmatic API
│ ├── output-formats.md
│ ├── allowlist.md
│ ├── ci-cd.md
│ ├── mr-pr-comments.md
│ ├── custom-patterns.md
│ ├── scoring.md
│ ├── testing.md
│ ├── limitations.md
│ └── releasing.md
├── src/
│ └── ansible_security_scanner/ # the Python package
│ ├── cli.py # CLI (--directory, --fix, --compliance, ...)
│ ├── scanner.py # Multi-pass orchestrator
│ ├── file_scanner.py # Per-file rules (line patterns, AST walkers)
│ ├── taint_tracker.py # Cross-file taint analysis
│ ├── fix_proposer.py # Dry-run unified-diff patch generator
│ ├── dependency_collector.py # SBOM inventory
│ ├── suppressions.py # Inline `# nosec` parser
│ ├── models.py # SecurityFinding, ScanReport, SecurityScore
│ ├── score_calculator.py # Severity-weighted scoring
│ ├── patterns_manager.py # Loads pattern YAML plugins
│ ├── patterns/ # 29+ YAML pattern plugins (auto-discovered)
│ ├── remediations/ # One module per category
│ └── formatters/ # markdown, json, xml, yaml, csv, html, junit, sarif, gitlab_sast, cyclonedx
└── tests/
├── test_integration.py # End-to-end scanner tests
├── test_formatters.py # Formatter unit tests
├── test_remediations.py # Remediation generator unit tests
└── playbooks/
├── bad_example.yml # Triggers every rule (100% coverage)
├── clean_example.yml # Zero findings (false-positive guard)
├── multi_example_bad/ # 6-file role fixture (cross-file taint)
└── multi_example_clean/ # 6-file hardened role fixture (zero findings)
Documentation
The long-form documentation is split by topic. Each page is a standalone
Markdown file in docs/ - GitHub renders them inline, and the
Hugo site at GitHub Pages serves the same content
with a navigation sidebar and search.
| Topic | Source |
|---|---|
| CLI flags, exit codes, suppressions, cross-file dedup | docs/cli.md |
Environment variables (auth, defaults, --changed-files) |
docs/environment.md |
| Programmatic Python API | docs/api.md |
| Output formats (Markdown, JSON, SARIF, GL-SAST, SBOM, ...) | docs/output-formats.md |
| Allowlist / suppressing findings | docs/allowlist.md |
| CI/CD integration (GitLab CI, GitHub Actions) | docs/ci-cd.md |
| MR / PR comments (auto-detected, self-hosted-aware) | docs/mr-pr-comments.md |
| Adding custom patterns | docs/custom-patterns.md |
| Security score model | docs/scoring.md |
| Testing | docs/testing.md |
| Limitations of static analysis | docs/limitations.md |
| Releasing | docs/releasing.md |
Documentation site
Full documentation is auto-generated from the docs/ Markdown
files plus the pattern YAML plugins, and published via Hugo + GitHub
Pages on every push to main.
The build pipeline (.github/workflows/scanner-docs.yml) runs:
build_docs.py- copies eachdocs/<slug>.mdinto Hugo'scontent/with appropriate front-matter, and generates one rule-table page per pattern category.- Hugo builds a static site with the Relearn theme.
- The site is deployed to GitHub Pages.
To preview locally:
# Generate content
python .hugo/scripts/build_docs.py
# Download theme (first time only)
cd .hugo
curl -sL https://github.com/McShelby/hugo-theme-relearn/archive/refs/heads/main.tar.gz | tar -xz -C themes/
mv themes/hugo-theme-relearn-main themes/hugo-theme-relearn
# Serve locally
hugo server
Requirements
- Python 3.11+
- PyYAML >= 6.0
- Jinja2 >= 3.0
- httpx >= 0.27 (used by
--github-comment/--gitlab-commentonly)
For development work from source:
pip install -e ".[dev]"
Contributing
See CONTRIBUTING.md for the full dev-environment
walkthrough, pattern-authoring guide, and PR checklist.
TL;DR for a first-time clone:
git clone --recurse-submodules https://github.com/cpeoples/ansible-security-scanner.git
cd ansible-security-scanner
python -m venv .venv && source .venv/bin/activate
python task.py install # editable install + test/lint/build deps
python task.py test # run the full pytest suite
python task.py scan ./tests/playbooks/bad_example.yml # try the scanner locally
Common contribution paths:
- Add a security pattern - drop a YAML plugin in
src/ansible_security_scanner/patterns/(auto-discovered at startup). See §3 ofCONTRIBUTING.mdfor the schema and validation steps. - Add a remediation generator - create a module in
src/ansible_security_scanner/remediations/, then wire it intoremediation_generator.py. - Add an output formatter - subclass
base.BaseFormatterundersrc/ansible_security_scanner/formatters/and register it inutils.get_formatter_class. - Run the gates before opening a PR -
python task.py test(full suite),python task.py lint,python task.py build.
Security
This repository runs three GitHub-native security checks on every push and pull request:
- Dependabot (
.github/dependabot.yml) - weekly update PRs forpipandgithub-actionsecosystems, plus out-of-band security advisories. - CodeQL (default setup, configured in repo settings) - SAST against
the Python in
src/ansible_security_scanner/. - Secret scanning + push protection - alerts on credential-shaped strings in tracked files.
Why some paths are excluded from secret scanning
Because this is a security scanner, the repo ships two structurally required corpora that look exactly like real secrets:
- Hand-curated negative fixtures (
tests/playbooks/bad_example.ymlandtests/playbooks/multi_example_bad/**) - the integration tests feed these to the scanner to assert each rule class fires correctly. They contain deliberate hardcoded credentials, fake AWS keys, and embedded SQS URLs; nothing here is a real credential. - Pattern-pack
vulnerable_examples:blocks (src/ansible_security_scanner/patterns/**) - rule definitions document, by design, the literal strings each rule is meant to match. These blocks are rendered into the generated rule docs that ship at https://cpeoples.github.io/ansible-security-scanner/.
Both surfaces are excluded via .github/secret_scanning.yml,
where each entry is annotated with the structural reason it's there.
Reporting a vulnerability
Open a private security advisory via the Security tab rather than a public issue. Vulnerabilities in the scanner itself (e.g. a code path that lets an attacker leak unredacted secrets through an MR comment) are in scope; vulnerabilities in Ansible playbooks detected by the scanner are not - those belong to the playbook's maintainer.
License & Attribution
This project is licensed under the Apache License, Version 2.0.
If you use, fork, embed, or build on this project, please retain attribution
to the original repository and contributors. The NOTICE file at
the repo root spells out exactly what is required.
In plain English:
- You can use this scanner commercially, modify it, embed it in larger tools, or build a paid product on top of it - no fee, no permission needed.
- You must keep the
LICENSEandNOTICEfiles in any redistribution. - If you fork this project or ship a derivative work (for example, a rebranded scanner, a hosted service, or a SaaS wrapper), you must state that it is derived from Ansible Security Scanner by Chris Peoples and link back to the original repository: https://github.com/cpeoples/ansible-security-scanner.
- The project name "Ansible Security Scanner" /
ansible-security-scanneris a reserved mark of the original author (Apache-2.0 §6). Your fork is welcome - under a different name. - Apache-2.0 includes an explicit patent grant and a retaliation clause: if you sue the project or its contributors over a patent related to the software, your rights under the license terminate automatically.
See NOTICE for the full attribution terms and
LICENSE for the Apache-2.0 text.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ansible_security_scanner-0.1.13.tar.gz.
File metadata
- Download URL: ansible_security_scanner-0.1.13.tar.gz
- Upload date:
- Size: 936.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dafaa58c2f4398a7b4d2619778acf01f63ff9a9c3c2004eb8b35adb418e2c7ed
|
|
| MD5 |
72ec618687a66286ef049b57d9b2c761
|
|
| BLAKE2b-256 |
175a2cb9092bb9e957c138f2675ffdc5190529f2e50fb5d86bb226e0f45ef0f2
|
Provenance
The following attestation bundles were made for ansible_security_scanner-0.1.13.tar.gz:
Publisher:
scanner-release.yml on cpeoples/ansible-security-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ansible_security_scanner-0.1.13.tar.gz -
Subject digest:
dafaa58c2f4398a7b4d2619778acf01f63ff9a9c3c2004eb8b35adb418e2c7ed - Sigstore transparency entry: 1597898133
- Sigstore integration time:
-
Permalink:
cpeoples/ansible-security-scanner@abe30a9bd60d686540546ddb30f1f6d24dcaafa4 -
Branch / Tag:
refs/tags/v0.1.13 - Owner: https://github.com/cpeoples
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
scanner-release.yml@abe30a9bd60d686540546ddb30f1f6d24dcaafa4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ansible_security_scanner-0.1.13-py3-none-any.whl.
File metadata
- Download URL: ansible_security_scanner-0.1.13-py3-none-any.whl
- Upload date:
- Size: 1.0 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c46afc85df296f6aa18cbf879576eeb040713737eae2a5568a2f7a70939abede
|
|
| MD5 |
bb682494d35d18325afb9d1e876bbfc6
|
|
| BLAKE2b-256 |
531c78dabad74d799d2b92df583b0ba496e62176322f55f58ca52d325002af1c
|
Provenance
The following attestation bundles were made for ansible_security_scanner-0.1.13-py3-none-any.whl:
Publisher:
scanner-release.yml on cpeoples/ansible-security-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ansible_security_scanner-0.1.13-py3-none-any.whl -
Subject digest:
c46afc85df296f6aa18cbf879576eeb040713737eae2a5568a2f7a70939abede - Sigstore transparency entry: 1597898232
- Sigstore integration time:
-
Permalink:
cpeoples/ansible-security-scanner@abe30a9bd60d686540546ddb30f1f6d24dcaafa4 -
Branch / Tag:
refs/tags/v0.1.13 - Owner: https://github.com/cpeoples
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
scanner-release.yml@abe30a9bd60d686540546ddb30f1f6d24dcaafa4 -
Trigger Event:
release
-
Statement type: