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.
Files changed (135) hide show
  1. ansible_security_scanner-0.1.0/.gitignore +132 -0
  2. ansible_security_scanner-0.1.0/CONTRIBUTING.md +381 -0
  3. ansible_security_scanner-0.1.0/LICENSE +201 -0
  4. ansible_security_scanner-0.1.0/NOTICE +56 -0
  5. ansible_security_scanner-0.1.0/PKG-INFO +312 -0
  6. ansible_security_scanner-0.1.0/README.md +270 -0
  7. ansible_security_scanner-0.1.0/pyproject.toml +150 -0
  8. ansible_security_scanner-0.1.0/src/ansible_security_scanner/__init__.py +71 -0
  9. ansible_security_scanner-0.1.0/src/ansible_security_scanner/__main__.py +10 -0
  10. ansible_security_scanner-0.1.0/src/ansible_security_scanner/_ast_helpers.py +105 -0
  11. ansible_security_scanner-0.1.0/src/ansible_security_scanner/_version.py +24 -0
  12. ansible_security_scanner-0.1.0/src/ansible_security_scanner/cli.py +1242 -0
  13. ansible_security_scanner-0.1.0/src/ansible_security_scanner/comment.py +2213 -0
  14. ansible_security_scanner-0.1.0/src/ansible_security_scanner/dependency_collector.py +323 -0
  15. ansible_security_scanner-0.1.0/src/ansible_security_scanner/file_scanner.py +5427 -0
  16. ansible_security_scanner-0.1.0/src/ansible_security_scanner/fix_proposer.py +578 -0
  17. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/__init__.py +31 -0
  18. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/base.py +116 -0
  19. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/csv.py +61 -0
  20. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/cyclonedx.py +243 -0
  21. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/gitlab_sast.py +352 -0
  22. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/html.py +958 -0
  23. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/json.py +198 -0
  24. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/junit.py +54 -0
  25. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/markdown.py +334 -0
  26. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/sarif.py +554 -0
  27. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/xml.py +88 -0
  28. ansible_security_scanner-0.1.0/src/ansible_security_scanner/formatters/yaml.py +18 -0
  29. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/atlas.yml +147 -0
  30. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cis_controls.yml +641 -0
  31. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cve.yml +277 -0
  32. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/cwe.yml +549 -0
  33. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/hipaa.yml +198 -0
  34. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/mitre_attack.yml +1417 -0
  35. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/nist_800_53.yml +558 -0
  36. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_appsec.yml +329 -0
  37. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_asvs.yml +1481 -0
  38. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/owasp_llm.yml +63 -0
  39. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/pci_dss.yml +443 -0
  40. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/soc2.yml +122 -0
  41. ansible_security_scanner-0.1.0/src/ansible_security_scanner/frameworks/stig.yml +225 -0
  42. ansible_security_scanner-0.1.0/src/ansible_security_scanner/link_resolver.py +523 -0
  43. ansible_security_scanner-0.1.0/src/ansible_security_scanner/models.py +99 -0
  44. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/__init__.py +3 -0
  45. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ai_ml_security.yml +635 -0
  46. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ansible_hygiene.yml +360 -0
  47. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/ansible_specific.yml +649 -0
  48. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/anti_forensics.yml +286 -0
  49. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/binary_planting.yml +129 -0
  50. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/command_injection.yml +541 -0
  51. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/dangerous_modules.yml +127 -0
  52. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/data_destruction.yml +140 -0
  53. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/data_exfiltration.yml +426 -0
  54. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/environment_hijacking.yml +163 -0
  55. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/external_urls.yml +230 -0
  56. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/hardcoded_credentials.yml +1874 -0
  57. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/insecure_communication.yml +1372 -0
  58. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/jinja_lookup_rce.yml +246 -0
  59. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/k8s_insecure_spec.yml +835 -0
  60. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/lateral_movement.yml +308 -0
  61. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/malicious_activity.yml +1096 -0
  62. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/obfuscation_evasion.yml +163 -0
  63. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/offensive_tools.yml +735 -0
  64. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/operational_security.yml +2429 -0
  65. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/privilege_escalation.yml +322 -0
  66. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/README.md +54 -0
  67. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ai_ml_security.yml +237 -0
  68. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ansible_hygiene.yml +135 -0
  69. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/ansible_specific.yml +212 -0
  70. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/anti_forensics.yml +60 -0
  71. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/binary_planting.yml +27 -0
  72. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/insecure_communication.yml +720 -0
  73. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/k8s_insecure_spec.yml +313 -0
  74. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/lateral_movement.yml +32 -0
  75. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/malicious_activity.yml +114 -0
  76. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/operational_security.yml +485 -0
  77. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/privilege_escalation.yml +58 -0
  78. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/supply_chain.yml +1059 -0
  79. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/system_compromise.yml +505 -0
  80. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/template_injection.yml +88 -0
  81. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/unauthorized_cloud_access.yml +1160 -0
  82. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/remediations/unsafe_permissions.yml +232 -0
  83. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/reverse_shells.yml +363 -0
  84. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/supply_chain.yml +2765 -0
  85. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/system_compromise.yml +1140 -0
  86. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/template_injection.yml +467 -0
  87. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/tunneling.yml +337 -0
  88. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/unauthorized_cloud_access.yml +3132 -0
  89. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/unsafe_permissions.yml +1035 -0
  90. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/variable_injection.yml +115 -0
  91. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/webhook_exposure.yml +110 -0
  92. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns/webshell_deployment.yml +246 -0
  93. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns_cli.py +148 -0
  94. ansible_security_scanner-0.1.0/src/ansible_security_scanner/patterns_manager.py +493 -0
  95. ansible_security_scanner-0.1.0/src/ansible_security_scanner/playbook_classifier.py +196 -0
  96. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/__init__.py +24 -0
  97. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_category_map.py +132 -0
  98. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_companion_index.py +39 -0
  99. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/_pattern_index.py +41 -0
  100. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ai_ml_security.py +254 -0
  101. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ansible_hygiene.py +292 -0
  102. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ansible_specific.py +858 -0
  103. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/base.py +213 -0
  104. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/become_delegate_misuse.py +365 -0
  105. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/callback_plugin_risk.py +145 -0
  106. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/command_injection.py +333 -0
  107. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/credentials.py +1092 -0
  108. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/curl.py +461 -0
  109. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/dangerous_modules.py +564 -0
  110. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/data_exfiltration.py +391 -0
  111. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/external_urls.py +569 -0
  112. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/galaxy_supply_chain.py +249 -0
  113. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/insecure_communication.py +411 -0
  114. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/k8s_insecure_spec.py +1071 -0
  115. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/malicious_activity.py +892 -0
  116. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/operational_security.py +642 -0
  117. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/permissions.py +1064 -0
  118. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/privilege_escalation.py +625 -0
  119. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/remediation_generator.py +396 -0
  120. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/rule_id_categories.yml +1191 -0
  121. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/ssh_trust_bypass.py +436 -0
  122. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/supply_chain.py +1066 -0
  123. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/system_compromise.py +655 -0
  124. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/taint_flow.py +156 -0
  125. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/template_injection.py +1494 -0
  126. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/unauthorized_cloud_access.py +1163 -0
  127. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/variables.py +68 -0
  128. ansible_security_scanner-0.1.0/src/ansible_security_scanner/remediations/vault_hygiene.py +254 -0
  129. ansible_security_scanner-0.1.0/src/ansible_security_scanner/scanner.py +621 -0
  130. ansible_security_scanner-0.1.0/src/ansible_security_scanner/score_calculator.py +152 -0
  131. ansible_security_scanner-0.1.0/src/ansible_security_scanner/suppressions.py +285 -0
  132. ansible_security_scanner-0.1.0/src/ansible_security_scanner/synthetic_rule_frameworks.py +262 -0
  133. ansible_security_scanner-0.1.0/src/ansible_security_scanner/taint_tracker.py +666 -0
  134. ansible_security_scanner-0.1.0/src/ansible_security_scanner/utils.py +113 -0
  135. 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.