ansible-security-scanner 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible_security_scanner-0.1.0/.gitignore +132 -0
- ansible_security_scanner-0.1.0/CONTRIBUTING.md +381 -0
- ansible_security_scanner-0.1.0/LICENSE +201 -0
- ansible_security_scanner-0.1.0/NOTICE +56 -0
- ansible_security_scanner-0.1.0/PKG-INFO +312 -0
- ansible_security_scanner-0.1.0/README.md +270 -0
- ansible_security_scanner-0.1.0/pyproject.toml +150 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/__init__.py +71 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/__main__.py +10 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/_ast_helpers.py +105 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/_version.py +24 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/cli.py +1242 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/comment.py +2213 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/dependency_collector.py +323 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/file_scanner.py +5427 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/fix_proposer.py +578 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/__init__.py +31 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/base.py +116 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/csv.py +61 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/cyclonedx.py +243 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/gitlab_sast.py +352 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/html.py +958 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/json.py +198 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/junit.py +54 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/markdown.py +334 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/sarif.py +554 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/xml.py +88 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/yaml.py +18 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/atlas.yml +147 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cis_controls.yml +641 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cve.yml +277 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cwe.yml +549 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/hipaa.yml +198 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/mitre_attack.yml +1417 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/nist_800_53.yml +558 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_appsec.yml +329 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_asvs.yml +1481 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_llm.yml +63 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/pci_dss.yml +443 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/soc2.yml +122 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/stig.yml +225 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/link_resolver.py +523 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/models.py +99 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/__init__.py +3 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ai_ml_security.yml +635 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ansible_hygiene.yml +360 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ansible_specific.yml +649 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/anti_forensics.yml +286 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/binary_planting.yml +129 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/command_injection.yml +541 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/dangerous_modules.yml +127 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/data_destruction.yml +140 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/data_exfiltration.yml +426 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/environment_hijacking.yml +163 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/external_urls.yml +230 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/hardcoded_credentials.yml +1874 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/insecure_communication.yml +1372 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/jinja_lookup_rce.yml +246 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/k8s_insecure_spec.yml +835 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/lateral_movement.yml +308 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/malicious_activity.yml +1096 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/obfuscation_evasion.yml +163 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/offensive_tools.yml +735 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/operational_security.yml +2429 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/privilege_escalation.yml +322 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/README.md +54 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ai_ml_security.yml +237 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ansible_hygiene.yml +135 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ansible_specific.yml +212 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/anti_forensics.yml +60 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/binary_planting.yml +27 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/insecure_communication.yml +720 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/k8s_insecure_spec.yml +313 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/lateral_movement.yml +32 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/malicious_activity.yml +114 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/operational_security.yml +485 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/privilege_escalation.yml +58 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/supply_chain.yml +1059 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/system_compromise.yml +505 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/template_injection.yml +88 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/unauthorized_cloud_access.yml +1160 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/unsafe_permissions.yml +232 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/reverse_shells.yml +363 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/supply_chain.yml +2765 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/system_compromise.yml +1140 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/template_injection.yml +467 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/tunneling.yml +337 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/unauthorized_cloud_access.yml +3132 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/unsafe_permissions.yml +1035 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/variable_injection.yml +115 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/webhook_exposure.yml +110 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/webshell_deployment.yml +246 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns_cli.py +148 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns_manager.py +493 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/playbook_classifier.py +196 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/__init__.py +24 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_category_map.py +132 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_companion_index.py +39 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_pattern_index.py +41 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ai_ml_security.py +254 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ansible_hygiene.py +292 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ansible_specific.py +858 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/base.py +213 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/become_delegate_misuse.py +365 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/callback_plugin_risk.py +145 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/command_injection.py +333 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/credentials.py +1092 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/curl.py +461 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/dangerous_modules.py +564 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/data_exfiltration.py +391 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/external_urls.py +569 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/galaxy_supply_chain.py +249 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/insecure_communication.py +411 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/k8s_insecure_spec.py +1071 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/malicious_activity.py +892 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/operational_security.py +642 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/permissions.py +1064 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/privilege_escalation.py +625 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/remediation_generator.py +396 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/rule_id_categories.yml +1191 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ssh_trust_bypass.py +436 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/supply_chain.py +1066 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/system_compromise.py +655 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/taint_flow.py +156 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/template_injection.py +1494 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/unauthorized_cloud_access.py +1163 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/variables.py +68 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/vault_hygiene.py +254 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/scanner.py +621 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/score_calculator.py +152 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/suppressions.py +285 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/synthetic_rule_frameworks.py +262 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/taint_tracker.py +666 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/utils.py +113 -0
- ansible_security_scanner-0.1.0/src/ansible_security_scanner/variable_extractor.py +330 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# -- Python bytecode / caches ------------------------------------------------
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
# -- Python packaging artifacts ----------------------------------------------
|
|
8
|
+
*.egg-info/
|
|
9
|
+
*.egg
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
wheels/
|
|
13
|
+
pip-wheel-metadata/
|
|
14
|
+
.eggs/
|
|
15
|
+
MANIFEST
|
|
16
|
+
|
|
17
|
+
# Generated by hatch-vcs on every build; source of truth is the git tag.
|
|
18
|
+
src/ansible_security_scanner/_version.py
|
|
19
|
+
|
|
20
|
+
# -- Virtual environments ----------------------------------------------------
|
|
21
|
+
# Ignore common venv names regardless of where they live, so a contributor
|
|
22
|
+
# using any of these conventions doesn't accidentally commit their local env.
|
|
23
|
+
.venv/
|
|
24
|
+
venv/
|
|
25
|
+
env/
|
|
26
|
+
ENV/
|
|
27
|
+
.env/
|
|
28
|
+
.virtualenv/
|
|
29
|
+
.python-version
|
|
30
|
+
|
|
31
|
+
# -- Tool caches -------------------------------------------------------------
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.mypy_cache/
|
|
34
|
+
.ruff_cache/
|
|
35
|
+
.pytype/
|
|
36
|
+
.tox/
|
|
37
|
+
.nox/
|
|
38
|
+
.cache/
|
|
39
|
+
.hypothesis/
|
|
40
|
+
|
|
41
|
+
# -- Coverage ----------------------------------------------------------------
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
htmlcov/
|
|
45
|
+
coverage.xml
|
|
46
|
+
*.cover
|
|
47
|
+
nosetests.xml
|
|
48
|
+
|
|
49
|
+
# -- Hugo (generated in CI) --------------------------------------------------
|
|
50
|
+
.hugo/content/
|
|
51
|
+
.hugo/public/
|
|
52
|
+
.hugo/resources/
|
|
53
|
+
.hugo/static/assets/
|
|
54
|
+
.hugo/static/images/
|
|
55
|
+
.hugo/.hugo_build.lock
|
|
56
|
+
|
|
57
|
+
# -- Editors / IDEs ----------------------------------------------------------
|
|
58
|
+
.vscode/
|
|
59
|
+
.idea/
|
|
60
|
+
.cursor/
|
|
61
|
+
*.swp
|
|
62
|
+
*.swo
|
|
63
|
+
*~
|
|
64
|
+
.project
|
|
65
|
+
.pydevproject
|
|
66
|
+
.spyderproject
|
|
67
|
+
.spyproject
|
|
68
|
+
|
|
69
|
+
# -- OS metadata -------------------------------------------------------------
|
|
70
|
+
.DS_Store
|
|
71
|
+
.DS_Store?
|
|
72
|
+
._*
|
|
73
|
+
.Spotlight-V100
|
|
74
|
+
.Trashes
|
|
75
|
+
ehthumbs.db
|
|
76
|
+
Thumbs.db
|
|
77
|
+
desktop.ini
|
|
78
|
+
|
|
79
|
+
# -- Logs & runtime ----------------------------------------------------------
|
|
80
|
+
*.log
|
|
81
|
+
*.pid
|
|
82
|
+
*.seed
|
|
83
|
+
*.pid.lock
|
|
84
|
+
|
|
85
|
+
# -- Secrets / local overrides (defense in depth) ----------------------------
|
|
86
|
+
# These should never be committed. Keeping them explicit even though they
|
|
87
|
+
# shouldn't be introduced in the first place.
|
|
88
|
+
.env
|
|
89
|
+
.env.local
|
|
90
|
+
.env.*.local
|
|
91
|
+
*.pem
|
|
92
|
+
*.key
|
|
93
|
+
!tests/**/*.pem
|
|
94
|
+
!tests/**/*.key
|
|
95
|
+
secrets.yml
|
|
96
|
+
!.security-scanner-allowlist.yml
|
|
97
|
+
|
|
98
|
+
# -- Scanner-generated reports and artifacts ---------------------------------
|
|
99
|
+
# The scanner emits reports in the repo root during local dev; keep them out
|
|
100
|
+
# of git. Allow-list the docs / meta markdown we DO want to track.
|
|
101
|
+
*.md
|
|
102
|
+
!README.md
|
|
103
|
+
!CONTRIBUTING.md
|
|
104
|
+
!RELEASING.md
|
|
105
|
+
!NOTICE.md
|
|
106
|
+
!/.github/**/*.md
|
|
107
|
+
!/docs/**/*.md
|
|
108
|
+
!/.hugo/**/*.md
|
|
109
|
+
*.csv
|
|
110
|
+
*.html
|
|
111
|
+
!/.hugo/**/*.html
|
|
112
|
+
*.xml
|
|
113
|
+
*.json
|
|
114
|
+
!/tests/data/*.json
|
|
115
|
+
*.sarif
|
|
116
|
+
*.cdx.json
|
|
117
|
+
*.patch
|
|
118
|
+
|
|
119
|
+
# -- Local scratch -----------------------------------------------------------
|
|
120
|
+
scratch/
|
|
121
|
+
tmp/
|
|
122
|
+
*.bak
|
|
123
|
+
*.orig
|
|
124
|
+
|
|
125
|
+
# -- Local playbook corpus for the stress-audit tool -------------------------
|
|
126
|
+
# Top-level audit/dev scripts live in /scripts; do not match nested paths
|
|
127
|
+
# like .hugo/scripts/ which contains the legitimate docs build helpers.
|
|
128
|
+
/scripts/
|
|
129
|
+
security_reports/
|
|
130
|
+
security-reports/
|
|
131
|
+
tests/playbooks/ansible/
|
|
132
|
+
tests/playbooks/multifile/
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# Contributing to Ansible Security Scanner
|
|
2
|
+
|
|
3
|
+
Thanks for taking the time to contribute. This guide covers both of the
|
|
4
|
+
common contribution flows:
|
|
5
|
+
|
|
6
|
+
1. **Adding or tuning a security pattern** (the most frequent kind of PR)
|
|
7
|
+
2. **Improving the scanner itself** (bug fixes, new formatters, remediation
|
|
8
|
+
logic, etc.)
|
|
9
|
+
|
|
10
|
+
Before you start, please read
|
|
11
|
+
[`NOTICE`](./NOTICE) - it lays out what's expected of anyone shipping a
|
|
12
|
+
derivative of this project (Apache 2.0 Section 4 + reserved project name).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Set up your dev environment
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone --recurse-submodules https://github.com/cpeoples/ansible-security-scanner.git
|
|
20
|
+
cd ansible-security-scanner
|
|
21
|
+
|
|
22
|
+
# If you already cloned without `--recurse-submodules`, pull the Hugo theme in now:
|
|
23
|
+
# git submodule update --init --recursive
|
|
24
|
+
|
|
25
|
+
python -m venv .venv
|
|
26
|
+
source .venv/bin/activate
|
|
27
|
+
|
|
28
|
+
# Installs the package in editable mode plus test/lint/build deps
|
|
29
|
+
python task.py install
|
|
30
|
+
|
|
31
|
+
# One-time: install the git pre-commit hook so ruff runs automatically
|
|
32
|
+
# on every commit. Without this, lint will only run in CI.
|
|
33
|
+
pre-commit install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Everything else in this file assumes that virtualenv is activated (or that
|
|
37
|
+
you'll let `task.py` auto-discover it under `./.venv`).
|
|
38
|
+
|
|
39
|
+
## 2. Use `task.py` for the common loops
|
|
40
|
+
|
|
41
|
+
`task.py` is a zero-dependency stdlib script that wraps the commands
|
|
42
|
+
contributors reach for most often. Run `python task.py help` for the full
|
|
43
|
+
list. The ones you'll use daily:
|
|
44
|
+
|
|
45
|
+
| Command | What it does |
|
|
46
|
+
| -------------------------------------- | ---------------------------------------------------- |
|
|
47
|
+
| `python task.py test` | Full pytest run (1327+ tests at the time of writing) |
|
|
48
|
+
| `python task.py test -- -k <expr>` | Filtered pytest run |
|
|
49
|
+
| `python task.py lint` | `ruff check` + `ruff format --check` + `mypy` if installed |
|
|
50
|
+
| `python task.py scan <path>` | Run the scanner against a local directory |
|
|
51
|
+
| `python task.py build` | Build wheel+sdist and run `twine check --strict` |
|
|
52
|
+
| `python task.py docs` | Regenerate the Hugo docs |
|
|
53
|
+
| `python task.py clean` | Remove `build/`, `dist/`, `.pytest_cache`, etc. |
|
|
54
|
+
|
|
55
|
+
You do not need to install `make`, `poetry`, or any other task runner.
|
|
56
|
+
|
|
57
|
+
## 3. Adding a new security pattern
|
|
58
|
+
|
|
59
|
+
Patterns live in YAML files under
|
|
60
|
+
`src/ansible_security_scanner/patterns/`. Each file is a plugin; the
|
|
61
|
+
scanner discovers them automatically at startup via `patterns_manager`.
|
|
62
|
+
|
|
63
|
+
### 3.1 Scaffold a new pattern file
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
python -m ansible_security_scanner.patterns_cli create "Your Pattern Name" your_patterns.yml
|
|
67
|
+
# Move the generated file into the plugins directory once you're happy with it.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3.2 Pattern file structure
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
name: "Short descriptive name shown in the plugin list"
|
|
74
|
+
author: "your-github-username"
|
|
75
|
+
description: "What class of vulnerabilities this plugin detects"
|
|
76
|
+
|
|
77
|
+
patterns:
|
|
78
|
+
- id: "unique_rule_id" # snake_case, globally unique
|
|
79
|
+
category: "hardcoded_credentials"
|
|
80
|
+
severity: "CRITICAL" # CRITICAL | HIGH | MEDIUM | LOW
|
|
81
|
+
title: "Human-readable title"
|
|
82
|
+
description: "One sentence explaining the risk"
|
|
83
|
+
regex: "your_regex_here"
|
|
84
|
+
recommendation: "Short actionable remediation hint"
|
|
85
|
+
# Optional enrichment fields - populated on the finding and consumed
|
|
86
|
+
# by formatters (SARIF tags, compliance filters, etc.):
|
|
87
|
+
cwe: ["CWE-798"]
|
|
88
|
+
mitre_attack: ["T1552.001"]
|
|
89
|
+
cis_controls: ["CIS-3.11"]
|
|
90
|
+
references: ["https://..."]
|
|
91
|
+
help_uri: "https://..."
|
|
92
|
+
precision: "high" # SARIF precision level
|
|
93
|
+
# Optional multi-line / evasion-resistant matching:
|
|
94
|
+
multiline: false
|
|
95
|
+
window: 10 # how many lines of context the regex sees
|
|
96
|
+
# Optional inline examples - the preferred way to lock rule behaviour
|
|
97
|
+
# in. ``tests/test_rule_examples.py`` auto-discovers both lists and
|
|
98
|
+
# asserts that every ``positive_examples`` entry triggers the rule
|
|
99
|
+
# while every ``negative_examples`` entry does NOT. A new rule with
|
|
100
|
+
# these fields gets free CI protection against regex drift.
|
|
101
|
+
positive_examples:
|
|
102
|
+
- "shell: curl -fsSL https://example.com/install.sh | bash"
|
|
103
|
+
- "ansible.builtin.shell: wget -qO- http://get.example.com | sh"
|
|
104
|
+
negative_examples:
|
|
105
|
+
- "shell: 'echo bash pipe test'" # unrelated bash mention
|
|
106
|
+
- "# shell: curl x | bash" # commented-out line
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3.3 Validate before you commit
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python -m ansible_security_scanner.patterns_cli validate path/to/your_patterns.yml
|
|
113
|
+
python -m ansible_security_scanner.patterns_cli list
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 3.4 Test your pattern
|
|
117
|
+
|
|
118
|
+
**Preferred:** add `positive_examples` / `negative_examples` inline in
|
|
119
|
+
the pattern YAML (see §3.2). The test runner at
|
|
120
|
+
`tests/test_rule_examples.py` auto-discovers every pattern file and
|
|
121
|
+
asserts each example - no new Python test file needed.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Run just the example-driven rule tests:
|
|
125
|
+
.venv/bin/python -m pytest tests/test_rule_examples.py -v
|
|
126
|
+
|
|
127
|
+
# Or run the full suite:
|
|
128
|
+
python task.py test
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If your rule needs more than a single-line example (cross-file taint,
|
|
132
|
+
multi-task flow, etc.), add a real fixture playbook under
|
|
133
|
+
`tests/playbooks/` plus a Python test:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# tests/test_your_patterns.py
|
|
137
|
+
def test_your_rule_detects_vulnerable_code(tmp_path):
|
|
138
|
+
pb = tmp_path / "bad.yml"
|
|
139
|
+
pb.write_text("- hosts: all\n tasks:\n - shell: 'echo AKIA{{ ... }}'\n")
|
|
140
|
+
# ...invoke the scanner, assert the finding...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
You can also quickly eyeball behaviour against a hand-written playbook:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python task.py scan ./examples/your_test_playbook.yml
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
To verify your new rule fires in isolation (no noise from other rules
|
|
150
|
+
on the fixture), use `--select`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
ansible-security-scanner --select <your_rule_id> --files examples/your_test_playbook.yml
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`--select` accepts comma-separated literals or fnmatch globs
|
|
157
|
+
(`--select aws_*`), and `--list-rules` prints every shipped rule_id one
|
|
158
|
+
per line on stdout. Both flags also honor the
|
|
159
|
+
`ANSIBLE_SEC_SCANNER_SELECT` / `_IGNORE` environment variables.
|
|
160
|
+
|
|
161
|
+
### 3.5 Renaming a rule ID (backward-compat aliases)
|
|
162
|
+
|
|
163
|
+
Rule IDs appear in user allowlists and inline `# nosec: <rule_id>`
|
|
164
|
+
suppressions, so renaming one is a breaking change for every downstream
|
|
165
|
+
consumer. Instead of hard-renaming, register the old name as an alias
|
|
166
|
+
in `src/ansible_security_scanner/suppressions.py`:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
_RULE_ID_ALIASES: dict[str, str] = {
|
|
170
|
+
# Format: {old_id: new_id}. One-way - the new ID is canonical.
|
|
171
|
+
"shell_with_dash_c": "shell_inline_compound_command",
|
|
172
|
+
"interpreter_with_dash_c": "interpreter_inline_code_execution",
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
With the alias in place, existing `# nosec: shell_with_dash_c
|
|
177
|
+
reason="..."` directives keep working unchanged; the scanner resolves
|
|
178
|
+
`shell_with_dash_c` -> `shell_inline_compound_command` before matching.
|
|
179
|
+
Drop the alias entry in a later release once consumers have migrated.
|
|
180
|
+
|
|
181
|
+
### 3.6 Cross-rule overlap suppression
|
|
182
|
+
|
|
183
|
+
When two rules fire on the same `(file, line)` they often describe the
|
|
184
|
+
same vulnerability from different angles - e.g. a line with `curl -fsSL
|
|
185
|
+
https://raw.githubusercontent.com/... | bash` matches every rule in the
|
|
186
|
+
pipe-to-shell family at once. To keep reports actionable, the scanner
|
|
187
|
+
deduplicates these via `_OVERLAP_SUPPRESSION_GROUPS` in
|
|
188
|
+
`src/ansible_security_scanner/file_scanner.py`:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
_OVERLAP_SUPPRESSION_GROUPS: tuple[tuple[str, ...], ...] = (
|
|
192
|
+
# Order inside a group is specificity - the leftmost rule wins
|
|
193
|
+
# when multiple members fire on the same line.
|
|
194
|
+
(
|
|
195
|
+
"raw_github_script_exec", # most specific
|
|
196
|
+
"curl_pipe_to_shell",
|
|
197
|
+
"curl_wget_pipe_shell_install_oneliner",
|
|
198
|
+
"download_pipe_to_shell",
|
|
199
|
+
"shell_pipe_to_interpreter", # most generic
|
|
200
|
+
),
|
|
201
|
+
# ... more groups ...
|
|
202
|
+
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
If your new rule genuinely describes the same vuln as an existing rule,
|
|
206
|
+
add both IDs to a group (ordered most-specific first) so the report
|
|
207
|
+
doesn't double-count. If you're intentionally carving out a *different*
|
|
208
|
+
failure mode on the same line, leave it out - two findings at the same
|
|
209
|
+
location is fine when they recommend different fixes.
|
|
210
|
+
|
|
211
|
+
Corresponding integration tests in `tests/test_integration.py` and
|
|
212
|
+
`tests/test_playbook_annotations.py` are already overlap-aware; `expect
|
|
213
|
+
<rule_id>` directives are satisfied if any member of the rule's overlap
|
|
214
|
+
group fires.
|
|
215
|
+
|
|
216
|
+
## 4. Writing good regex patterns
|
|
217
|
+
|
|
218
|
+
The scanner pre-compiles every pattern once (see
|
|
219
|
+
`SecurityPattern.__post_init__` in `patterns_manager.py`), so pattern cost
|
|
220
|
+
at scan time is basically the regex's own complexity. Keep them fast and
|
|
221
|
+
specific:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
# Good - word boundary, exact AWS key format
|
|
225
|
+
regex: "\\bAKIA[0-9A-Z]{16}\\b"
|
|
226
|
+
|
|
227
|
+
# Good - escaped literal, anchored
|
|
228
|
+
regex: "docker\\.sock"
|
|
229
|
+
|
|
230
|
+
# Good - avoids matching in comments
|
|
231
|
+
regex: "^(?!\\s*#)\\s*password:"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Avoid:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
regex: "password" # too broad - flags every doc comment
|
|
238
|
+
regex: "password: \"admin123\"" # too specific - only catches this literal
|
|
239
|
+
regex: "password: [unclosed" # invalid - scanner logs a warning and skips
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Use the `category` that matches an existing remediation generator where
|
|
243
|
+
possible - otherwise your finding will render with a generic remediation
|
|
244
|
+
template. The full list of categories lives in
|
|
245
|
+
`src/ansible_security_scanner/remediations/_category_map.py`.
|
|
246
|
+
|
|
247
|
+
### 4.1 Remediation quality contract
|
|
248
|
+
|
|
249
|
+
Every shipped rule must produce a remediation that (a) is specific to
|
|
250
|
+
the rule and (b) ships a copy-pasteable Ansible fix. Two contract tests
|
|
251
|
+
in `tests/test_remediations.py` enforce this:
|
|
252
|
+
|
|
253
|
+
- `test_remediation_is_relevant_to_the_rule` - the output must mention a
|
|
254
|
+
distinctive token from the rule's own `title` or `recommendation`,
|
|
255
|
+
and must not regress to legacy boilerplate.
|
|
256
|
+
- `test_remediation_includes_secure_fix_yaml_block` - the output must
|
|
257
|
+
contain a `✅ Secure Fix` heading followed by a fenced ```yaml block.
|
|
258
|
+
|
|
259
|
+
Three ways to satisfy the Secure Fix contract, in order of preference:
|
|
260
|
+
|
|
261
|
+
1. Add `negative_examples:` to the rule in its pattern YAML. The metadata
|
|
262
|
+
renderer renders the first entry as the Secure Fix block.
|
|
263
|
+
2. Add a `secure_fix:` entry under the rule's id in
|
|
264
|
+
`src/ansible_security_scanner/patterns/remediations/<category>.yml`
|
|
265
|
+
(preferred when the fix is large enough to deserve its own diff).
|
|
266
|
+
3. Add a tailored handler in `remediations/<category>.py` that emits its
|
|
267
|
+
own `✅ Secure Fix` heading + fenced ```yaml block.
|
|
268
|
+
|
|
269
|
+
If the rule's correct response is procedural (escalate, audit, run via
|
|
270
|
+
a reviewed IaC pipeline, vendor patch) and a copy-pasteable Ansible task
|
|
271
|
+
would actively mislead the user, set `no_ansible_remediation: true` on
|
|
272
|
+
the rule's pattern YAML entry instead. The renderer then emits a
|
|
273
|
+
`✅ Secure Response` prose block sourced from `recommendation:`, which
|
|
274
|
+
this contract treats as compliant.
|
|
275
|
+
|
|
276
|
+
The repo ships a helper that batch-applies the `no_ansible_remediation`
|
|
277
|
+
flag from a curated list at `scripts/data/procedural_rule_ids.txt`:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
python scripts/stamp_no_ansible_remediation.py
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The script is idempotent. Add a rule_id to `procedural_rule_ids.txt` and
|
|
284
|
+
re-run; remove and re-run to revert.
|
|
285
|
+
|
|
286
|
+
### 4.2 New rule contribution checklist
|
|
287
|
+
|
|
288
|
+
Every new rule has to clear the following contracts before CI will let it
|
|
289
|
+
land. Each item maps to a parametrised test that runs once per shipped
|
|
290
|
+
rule, so a missing field is reported with the offending `rule_id` rather
|
|
291
|
+
than a generic failure.
|
|
292
|
+
|
|
293
|
+
| # | Requirement | Enforced by |
|
|
294
|
+
|---|---|---|
|
|
295
|
+
| 1 | All seven required fields are populated and non-empty: `id`, `title`, `description`, `severity`, `category`, `recommendation`, `regex` (severity is one of `INFO`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). | `tests/test_release_contract.py::test_required_metadata_present` |
|
|
296
|
+
| 2 | `description` is at least **60 characters**. Stub one-liners are rejected; expand to describe the threat model, attacker capability, or specific shape the regex matches. | `tests/test_release_contract.py::test_description_meets_minimum_length` |
|
|
297
|
+
| 3 | At least one framework tag is set: any of `cwe`, `mitre_attack`, `cis_controls`, `nist_controls`, `owasp_appsec`, `owasp_asvs`, `owasp`, `pci_dss`, `hipaa`, `soc2`, `iso27001`, or `stig`. This is what the compliance filter pivots on. | `tests/test_release_contract.py::test_every_rule_has_a_framework_tag` |
|
|
298
|
+
| 4 | At least one `positive_examples` entry. Each entry must match the rule's regex under `re.IGNORECASE \| re.MULTILINE`. AST-walker rules (`regex: "(?!)"`) and rules in `SYNTHETIC_RULE_IDS` are exempt. | `tests/test_release_contract.py::test_every_finding_rule_has_a_positive_example` and `tests/test_rule_examples.py` |
|
|
299
|
+
| 5 | Every `negative_examples` entry must NOT match the regex. (Same flags.) Lock in the FP shape that motivated the carve-out. | `tests/test_rule_examples.py` |
|
|
300
|
+
| 6 | The remediation includes a `✅ Secure Fix` heading and a fenced ```yaml block AND mentions a distinctive token from the rule's `title` / `recommendation`. Three legal ways to satisfy this - see §4.1. Procedural-only rules opt out via `no_ansible_remediation: true`. | `tests/test_remediations.py::test_remediation_is_relevant_to_the_rule`, `tests/test_remediations.py::test_remediation_includes_secure_fix_yaml_block` |
|
|
301
|
+
| 7 | If your rule adds a new CLI flag, document it in `README.md` (the `## CLI Reference` section). | `tests/test_release_contract.py::test_every_argparse_flag_is_documented_in_readme` |
|
|
302
|
+
| 8 | If you add a name to `ansible_security_scanner.__all__`, document it in `README.md` (the `## Programmatic API` section). | `tests/test_release_contract.py::test_every_public_api_symbol_is_documented_in_readme` |
|
|
303
|
+
|
|
304
|
+
Run the gates locally before opening a PR:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
.venv/bin/python -m pytest tests/test_release_contract.py tests/test_remediations.py tests/test_rule_examples.py -q
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
When a contract fails, the test name + `rule_id` in the parametrised test
|
|
311
|
+
id tells you exactly which rule and which contract to fix - no log-diving
|
|
312
|
+
required. If you can't satisfy a contract for a legitimate reason (e.g.
|
|
313
|
+
the rule is an AST sentinel), the right move is to extend the carve-out
|
|
314
|
+
rules in the test rather than to lower the bar.
|
|
315
|
+
|
|
316
|
+
## 5. Severity guidelines
|
|
317
|
+
|
|
318
|
+
| Severity | Use when... |
|
|
319
|
+
| --------- | ----------------------------------------------------------- |
|
|
320
|
+
| CRITICAL | Immediate compromise: hardcoded secret, RCE, root on host |
|
|
321
|
+
| HIGH | Clear path to privilege escalation, dangerous cloud action |
|
|
322
|
+
| MEDIUM | Weakness that increases attack surface but isn't exploitable alone |
|
|
323
|
+
| LOW | Best-practice violation, stale config, missing hardening |
|
|
324
|
+
|
|
325
|
+
Findings are scored by severity in `score_calculator.py`. Over-tagging as
|
|
326
|
+
CRITICAL breaks security-gate thresholds in downstream CI pipelines, so be
|
|
327
|
+
honest about impact.
|
|
328
|
+
|
|
329
|
+
## 6. Improving the scanner itself
|
|
330
|
+
|
|
331
|
+
Bug fixes, new output formatters, remediation improvements, performance work
|
|
332
|
+
- all welcome. Before opening a PR:
|
|
333
|
+
|
|
334
|
+
1. Run the full test suite: `python task.py test` (must be green).
|
|
335
|
+
2. Run lint: `python task.py lint`.
|
|
336
|
+
3. Build + validate the package: `python task.py build` (wheel + sdist must
|
|
337
|
+
pass `twine check --strict`).
|
|
338
|
+
4. If your change affects the CLI or output formats, update the
|
|
339
|
+
auto-generated docs: `python task.py docs`.
|
|
340
|
+
|
|
341
|
+
### Commit & PR style
|
|
342
|
+
|
|
343
|
+
- Keep commits focused. A pattern file addition is one PR; a scanner
|
|
344
|
+
refactor is another.
|
|
345
|
+
- If you're touching suppressions, taint tracking, or the dispatch table
|
|
346
|
+
in `remediation_generator.py`, add a regression test that would have
|
|
347
|
+
failed before your fix.
|
|
348
|
+
- Attribution matters: you keep your commit authorship on the PR, and
|
|
349
|
+
you'll be listed as a contributor in the project history.
|
|
350
|
+
|
|
351
|
+
## 7. Where things live
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
src/ansible_security_scanner/
|
|
355
|
+
├── cli.py # argparse entrypoint -> main()
|
|
356
|
+
├── scanner.py # Orchestrator: reads files once, dispatches to FileScanner / TaintTracker / DependencyCollector / FixProposer.
|
|
357
|
+
├── file_scanner.py # Per-file scanning (line patterns, structural walkers, Jinja2 AST, suppressions).
|
|
358
|
+
├── taint_tracker.py # Cross-file taint analysis.
|
|
359
|
+
├── fix_proposer.py # Dry-run unified-diff patches for --fix.
|
|
360
|
+
├── dependency_collector.py # Builds the SBOM component inventory from Ansible/Galaxy/pip/bindep/EE manifests.
|
|
361
|
+
├── _ast_helpers.py # Shared YAML walkers used by the above.
|
|
362
|
+
├── patterns_manager.py # YAML plugin loader + SecurityPattern (with cached compiled regex)
|
|
363
|
+
├── patterns/*.yml # Every shipped detection rule
|
|
364
|
+
├── remediations/
|
|
365
|
+
│ ├── remediation_generator.py # Dispatch - one entry per category
|
|
366
|
+
│ ├── _category_map.py # rule_id -> category lookup + keyword fallback
|
|
367
|
+
│ └── <category>.py # Per-category fix renderers
|
|
368
|
+
├── formatters/ # Markdown, JSON, SARIF, CycloneDX, ...
|
|
369
|
+
├── comment.py # GitHub/GitLab MR/PR commenter (env-only platform detection, httpx clients, marker-based edit-in-place, Dashboard+Drilldown renderer).
|
|
370
|
+
└── utils.py # Argument helpers, logging setup, exit codes
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## 8. Getting help
|
|
374
|
+
|
|
375
|
+
- Check existing patterns in `src/ansible_security_scanner/patterns/`
|
|
376
|
+
for examples.
|
|
377
|
+
- Open a GitHub issue if you're unsure whether a change is in scope.
|
|
378
|
+
- Look at the test files in `tests/` for the expected shape of a
|
|
379
|
+
new regression test.
|
|
380
|
+
|
|
381
|
+
Happy contributing.
|