appsec-rules-pack 0.2.0__tar.gz → 0.3.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.
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/PKG-INFO +122 -14
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/README.md +121 -13
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/pyproject.toml +3 -1
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/__init__.py +1 -1
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/cli.py +32 -8
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/validator.py +11 -7
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/PKG-INFO +122 -14
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/SOURCES.txt +6 -0
- appsec_rules_pack-0.3.0/tests/test_cli_export_output.py +69 -0
- appsec_rules_pack-0.3.0/tests/test_cli_output_errors.py +60 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_edge_cases.py +44 -0
- appsec_rules_pack-0.3.0/tests/test_export_degradation.py +92 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_exporter.py +1 -1
- appsec_rules_pack-0.3.0/tests/test_helpers.py +33 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_packaging.py +5 -14
- appsec_rules_pack-0.3.0/tests/test_readme_example.py +62 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_reporter.py +19 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_validator.py +50 -0
- appsec_rules_pack-0.3.0/tests/test_validator_degradation.py +80 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_validator_paths.py +5 -11
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/LICENSE +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/setup.cfg +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/__main__.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/exporter.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/loader.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/reporter.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/sarif_export.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/schemas/appsec-rule.schema.json +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack/semgrep_scaffold.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/dependency_links.txt +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/entry_points.txt +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/requires.txt +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/src/appsec_rules_pack.egg-info/top_level.txt +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_examples.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_lifecycle_and_mappings.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_loader.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_sarif_export.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_schema.py +0 -0
- {appsec_rules_pack-0.2.0 → appsec_rules_pack-0.3.0}/tests/test_semgrep_scaffold.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appsec-rules-pack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Generic AppSec policy-as-code rules pack and validator.
|
|
5
5
|
Author: Lucas Henrique Grifoni
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -38,6 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
[](https://scorecard.dev/viewer/?uri=github.com/lucashgrifoni/AppSec-Rules-Pack)
|
|
39
39
|
[](LICENSE)
|
|
40
40
|
[](pyproject.toml)
|
|
41
|
+
[](https://pypi.org/project/appsec-rules-pack/)
|
|
41
42
|
|
|
42
43
|
Reusable AppSec policy-as-code rules for secure application review, CI quality gates,
|
|
43
44
|
and manual evidence collection.
|
|
@@ -69,9 +70,11 @@ configuration.
|
|
|
69
70
|
enum/type/additionalProperties failures, duplicate rule IDs, cross-file
|
|
70
71
|
duplicate IDs, exception-window warnings, exception-policy contradictions,
|
|
71
72
|
malformed framework mapping IDs, and sensitive-value detection.
|
|
72
|
-
- A
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
- A 95% coverage gate plus a hardened CI/CD surface: a build/lint/test workflow (with
|
|
74
|
+
separate jobs running the suite on Ubuntu and Windows, and on Python 3.13), a security
|
|
75
|
+
pipeline (Semgrep, CodeQL, Bandit, Trivy, KICS, pip-audit, Gitleaks, Dependency Review,
|
|
76
|
+
actionlint), and OpenSSF Scorecard analysis. The build/lint/test, cross-platform, and
|
|
77
|
+
security jobs are required status checks on `master`.
|
|
75
78
|
- Contribution guidance for safe rule additions, a code of conduct, and issue/PR
|
|
76
79
|
templates.
|
|
77
80
|
- A CI integration template in `examples/`.
|
|
@@ -87,6 +90,8 @@ configuration.
|
|
|
87
90
|
| | `-- rule_proposal.md
|
|
88
91
|
| |-- workflows/
|
|
89
92
|
| | |-- ci.yml
|
|
93
|
+
| | |-- policy-gate.yml
|
|
94
|
+
| | |-- publish-pypi.yml
|
|
90
95
|
| | |-- scorecard.yml
|
|
91
96
|
| | `-- security-ci-cd.yml
|
|
92
97
|
| |-- CODEOWNERS
|
|
@@ -94,6 +99,12 @@ configuration.
|
|
|
94
99
|
| `-- dependabot.yml
|
|
95
100
|
|-- examples/
|
|
96
101
|
| `-- README.md
|
|
102
|
+
|-- exports/
|
|
103
|
+
| |-- appsec-baseline.index.json
|
|
104
|
+
| |-- sarif/
|
|
105
|
+
| | `-- appsec-baseline.sarif.json
|
|
106
|
+
| `-- semgrep/
|
|
107
|
+
| `-- appsec-baseline.semgrep.yaml
|
|
97
108
|
|-- rules/
|
|
98
109
|
| `-- appsec-baseline.yaml
|
|
99
110
|
|-- src/
|
|
@@ -101,7 +112,11 @@ configuration.
|
|
|
101
112
|
| |-- __init__.py
|
|
102
113
|
| |-- __main__.py
|
|
103
114
|
| |-- cli.py
|
|
115
|
+
| |-- exporter.py
|
|
104
116
|
| |-- loader.py
|
|
117
|
+
| |-- reporter.py
|
|
118
|
+
| |-- sarif_export.py
|
|
119
|
+
| |-- semgrep_scaffold.py
|
|
105
120
|
| |-- validator.py
|
|
106
121
|
| `-- schemas/
|
|
107
122
|
| `-- appsec-rule.schema.json
|
|
@@ -112,12 +127,11 @@ configuration.
|
|
|
112
127
|
| | |-- fail/
|
|
113
128
|
| | |-- pass/
|
|
114
129
|
| | `-- warn/
|
|
115
|
-
| |--
|
|
116
|
-
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| `-- test_validator_paths.py
|
|
130
|
+
| |-- helpers.py # shared subprocess helper for tests that shell out
|
|
131
|
+
| `-- test_*.py # one module per validated behaviour
|
|
132
|
+
|-- .gitattributes
|
|
133
|
+
|-- .gitignore
|
|
134
|
+
|-- .gitleaks.toml
|
|
121
135
|
|-- CHANGELOG.md
|
|
122
136
|
|-- CODE_OF_CONDUCT.md
|
|
123
137
|
|-- CONTRIBUTING.md
|
|
@@ -130,7 +144,31 @@ configuration.
|
|
|
130
144
|
`-- pyproject.toml
|
|
131
145
|
```
|
|
132
146
|
|
|
133
|
-
##
|
|
147
|
+
## Installation
|
|
148
|
+
|
|
149
|
+
Install the published package from PyPI:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install appsec-rules-pack
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This installs the `appsec-rules` console script. Pin to a reviewed version
|
|
156
|
+
(for example `appsec-rules-pack==0.3.0`) when using it in a CI quality gate.
|
|
157
|
+
|
|
158
|
+
**What the distribution contains:** the validator, the CLI, and the JSON Schema. It does
|
|
159
|
+
not ship a rules pack — the CLI validates whatever path you point it at.
|
|
160
|
+
|
|
161
|
+
To get the baseline pack of 19 rules, either take the version-pinned copy attached to each
|
|
162
|
+
GitHub Release:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
curl -LO https://github.com/lucashgrifoni/AppSec-Rules-Pack/releases/download/v0.3.0/appsec-baseline.yaml
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
or copy [`rules/appsec-baseline.yaml`](rules/appsec-baseline.yaml) from this repository.
|
|
169
|
+
You can also write your own pack from the example below.
|
|
170
|
+
|
|
171
|
+
### From source (development)
|
|
134
172
|
|
|
135
173
|
```powershell
|
|
136
174
|
python -m venv .venv
|
|
@@ -147,8 +185,78 @@ python -m venv .venv
|
|
|
147
185
|
If the dependencies already exist in the active Python environment, the validator can
|
|
148
186
|
also be run directly with `PYTHONPATH=src`.
|
|
149
187
|
|
|
188
|
+
## Your First Rules Pack
|
|
189
|
+
|
|
190
|
+
A pack is a `pack` block plus one or more `rules`. This is a complete, valid minimal pack —
|
|
191
|
+
copy it, run `appsec-rules validate` on it, then grow it. Every field shown is required; the
|
|
192
|
+
full contract is in
|
|
193
|
+
[`appsec-rule.schema.json`](src/appsec_rules_pack/schemas/appsec-rule.schema.json), and the
|
|
194
|
+
19 baseline rules in [`rules/appsec-baseline.yaml`](rules/appsec-baseline.yaml) are worked
|
|
195
|
+
examples.
|
|
196
|
+
|
|
197
|
+
<!-- readme-example:minimal-pack (validated by tests/test_readme_example.py) -->
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
pack:
|
|
201
|
+
id: my-pack
|
|
202
|
+
name: My Rules Pack
|
|
203
|
+
version: 0.1.0
|
|
204
|
+
mode: advisory
|
|
205
|
+
owner: appsec
|
|
206
|
+
description: A minimal rules pack to start from.
|
|
207
|
+
|
|
208
|
+
rules:
|
|
209
|
+
- id: APPSEC-EXAMPLE-001
|
|
210
|
+
title: Require controlled error handling
|
|
211
|
+
description: Verify that invalid user input returns controlled errors without stack traces.
|
|
212
|
+
severity: medium
|
|
213
|
+
category: configuration
|
|
214
|
+
status: enabled
|
|
215
|
+
enforcement: advisory
|
|
216
|
+
targets:
|
|
217
|
+
- api
|
|
218
|
+
mappings:
|
|
219
|
+
owasp_asvs:
|
|
220
|
+
- V14.4
|
|
221
|
+
owasp_api_top_10_2023:
|
|
222
|
+
- API8:2023
|
|
223
|
+
cwe:
|
|
224
|
+
- CWE-209
|
|
225
|
+
nist_ssdf:
|
|
226
|
+
- PW.7
|
|
227
|
+
evidence:
|
|
228
|
+
required:
|
|
229
|
+
- Error handling path returns a documented response shape.
|
|
230
|
+
signals:
|
|
231
|
+
- Negative tests assert controlled error messages for invalid input.
|
|
232
|
+
match:
|
|
233
|
+
type: review
|
|
234
|
+
includes:
|
|
235
|
+
- API handlers that process untrusted input.
|
|
236
|
+
excludes:
|
|
237
|
+
- Local developer-only scripts that are not shipped.
|
|
238
|
+
remediation:
|
|
239
|
+
guidance: Replace raw exception output with a controlled error contract.
|
|
240
|
+
validation:
|
|
241
|
+
- Run negative tests for invalid input and malformed requests.
|
|
242
|
+
exceptions:
|
|
243
|
+
allowed: true
|
|
244
|
+
max_days: 30
|
|
245
|
+
required_fields:
|
|
246
|
+
- owner
|
|
247
|
+
- justification
|
|
248
|
+
- expires_at
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Note the shapes that are easy to guess wrong: `evidence` and `match` are objects (not
|
|
252
|
+
lists), `remediation` needs `guidance`, and `required_fields` accepts only `owner`,
|
|
253
|
+
`justification`, `expires_at`, `compensating_control`, and `validation_plan`.
|
|
254
|
+
|
|
150
255
|
## Usage
|
|
151
256
|
|
|
257
|
+
The examples below use `rules/appsec-baseline.yaml`, which exists in **this repository**.
|
|
258
|
+
If you installed from PyPI, point the CLI at your own rules file or directory instead.
|
|
259
|
+
|
|
152
260
|
Validate the baseline rules pack:
|
|
153
261
|
|
|
154
262
|
```bash
|
|
@@ -205,9 +313,9 @@ code is non-zero when validation fails, matching the text output.
|
|
|
205
313
|
|
|
206
314
|
## Use It In Your CI
|
|
207
315
|
|
|
208
|
-
`examples/README.md` contains a GitHub Actions template for installing the pack
|
|
209
|
-
validating a rules directory, and failing the build on errors (and optionally
|
|
210
|
-
warnings). Pin the
|
|
316
|
+
`examples/README.md` contains a GitHub Actions template for installing the pack from
|
|
317
|
+
PyPI, validating a rules directory, and failing the build on errors (and optionally
|
|
318
|
+
warnings). Pin the install to a reviewed release version before enabling it as a
|
|
211
319
|
quality gate.
|
|
212
320
|
|
|
213
321
|
## Rule Pack Model
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
[](https://scorecard.dev/viewer/?uri=github.com/lucashgrifoni/AppSec-Rules-Pack)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](pyproject.toml)
|
|
8
|
+
[](https://pypi.org/project/appsec-rules-pack/)
|
|
8
9
|
|
|
9
10
|
Reusable AppSec policy-as-code rules for secure application review, CI quality gates,
|
|
10
11
|
and manual evidence collection.
|
|
@@ -36,9 +37,11 @@ configuration.
|
|
|
36
37
|
enum/type/additionalProperties failures, duplicate rule IDs, cross-file
|
|
37
38
|
duplicate IDs, exception-window warnings, exception-policy contradictions,
|
|
38
39
|
malformed framework mapping IDs, and sensitive-value detection.
|
|
39
|
-
- A
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
- A 95% coverage gate plus a hardened CI/CD surface: a build/lint/test workflow (with
|
|
41
|
+
separate jobs running the suite on Ubuntu and Windows, and on Python 3.13), a security
|
|
42
|
+
pipeline (Semgrep, CodeQL, Bandit, Trivy, KICS, pip-audit, Gitleaks, Dependency Review,
|
|
43
|
+
actionlint), and OpenSSF Scorecard analysis. The build/lint/test, cross-platform, and
|
|
44
|
+
security jobs are required status checks on `master`.
|
|
42
45
|
- Contribution guidance for safe rule additions, a code of conduct, and issue/PR
|
|
43
46
|
templates.
|
|
44
47
|
- A CI integration template in `examples/`.
|
|
@@ -54,6 +57,8 @@ configuration.
|
|
|
54
57
|
| | `-- rule_proposal.md
|
|
55
58
|
| |-- workflows/
|
|
56
59
|
| | |-- ci.yml
|
|
60
|
+
| | |-- policy-gate.yml
|
|
61
|
+
| | |-- publish-pypi.yml
|
|
57
62
|
| | |-- scorecard.yml
|
|
58
63
|
| | `-- security-ci-cd.yml
|
|
59
64
|
| |-- CODEOWNERS
|
|
@@ -61,6 +66,12 @@ configuration.
|
|
|
61
66
|
| `-- dependabot.yml
|
|
62
67
|
|-- examples/
|
|
63
68
|
| `-- README.md
|
|
69
|
+
|-- exports/
|
|
70
|
+
| |-- appsec-baseline.index.json
|
|
71
|
+
| |-- sarif/
|
|
72
|
+
| | `-- appsec-baseline.sarif.json
|
|
73
|
+
| `-- semgrep/
|
|
74
|
+
| `-- appsec-baseline.semgrep.yaml
|
|
64
75
|
|-- rules/
|
|
65
76
|
| `-- appsec-baseline.yaml
|
|
66
77
|
|-- src/
|
|
@@ -68,7 +79,11 @@ configuration.
|
|
|
68
79
|
| |-- __init__.py
|
|
69
80
|
| |-- __main__.py
|
|
70
81
|
| |-- cli.py
|
|
82
|
+
| |-- exporter.py
|
|
71
83
|
| |-- loader.py
|
|
84
|
+
| |-- reporter.py
|
|
85
|
+
| |-- sarif_export.py
|
|
86
|
+
| |-- semgrep_scaffold.py
|
|
72
87
|
| |-- validator.py
|
|
73
88
|
| `-- schemas/
|
|
74
89
|
| `-- appsec-rule.schema.json
|
|
@@ -79,12 +94,11 @@ configuration.
|
|
|
79
94
|
| | |-- fail/
|
|
80
95
|
| | |-- pass/
|
|
81
96
|
| | `-- warn/
|
|
82
|
-
| |--
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
| `-- test_validator_paths.py
|
|
97
|
+
| |-- helpers.py # shared subprocess helper for tests that shell out
|
|
98
|
+
| `-- test_*.py # one module per validated behaviour
|
|
99
|
+
|-- .gitattributes
|
|
100
|
+
|-- .gitignore
|
|
101
|
+
|-- .gitleaks.toml
|
|
88
102
|
|-- CHANGELOG.md
|
|
89
103
|
|-- CODE_OF_CONDUCT.md
|
|
90
104
|
|-- CONTRIBUTING.md
|
|
@@ -97,7 +111,31 @@ configuration.
|
|
|
97
111
|
`-- pyproject.toml
|
|
98
112
|
```
|
|
99
113
|
|
|
100
|
-
##
|
|
114
|
+
## Installation
|
|
115
|
+
|
|
116
|
+
Install the published package from PyPI:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pip install appsec-rules-pack
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This installs the `appsec-rules` console script. Pin to a reviewed version
|
|
123
|
+
(for example `appsec-rules-pack==0.3.0`) when using it in a CI quality gate.
|
|
124
|
+
|
|
125
|
+
**What the distribution contains:** the validator, the CLI, and the JSON Schema. It does
|
|
126
|
+
not ship a rules pack — the CLI validates whatever path you point it at.
|
|
127
|
+
|
|
128
|
+
To get the baseline pack of 19 rules, either take the version-pinned copy attached to each
|
|
129
|
+
GitHub Release:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
curl -LO https://github.com/lucashgrifoni/AppSec-Rules-Pack/releases/download/v0.3.0/appsec-baseline.yaml
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
or copy [`rules/appsec-baseline.yaml`](rules/appsec-baseline.yaml) from this repository.
|
|
136
|
+
You can also write your own pack from the example below.
|
|
137
|
+
|
|
138
|
+
### From source (development)
|
|
101
139
|
|
|
102
140
|
```powershell
|
|
103
141
|
python -m venv .venv
|
|
@@ -114,8 +152,78 @@ python -m venv .venv
|
|
|
114
152
|
If the dependencies already exist in the active Python environment, the validator can
|
|
115
153
|
also be run directly with `PYTHONPATH=src`.
|
|
116
154
|
|
|
155
|
+
## Your First Rules Pack
|
|
156
|
+
|
|
157
|
+
A pack is a `pack` block plus one or more `rules`. This is a complete, valid minimal pack —
|
|
158
|
+
copy it, run `appsec-rules validate` on it, then grow it. Every field shown is required; the
|
|
159
|
+
full contract is in
|
|
160
|
+
[`appsec-rule.schema.json`](src/appsec_rules_pack/schemas/appsec-rule.schema.json), and the
|
|
161
|
+
19 baseline rules in [`rules/appsec-baseline.yaml`](rules/appsec-baseline.yaml) are worked
|
|
162
|
+
examples.
|
|
163
|
+
|
|
164
|
+
<!-- readme-example:minimal-pack (validated by tests/test_readme_example.py) -->
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
pack:
|
|
168
|
+
id: my-pack
|
|
169
|
+
name: My Rules Pack
|
|
170
|
+
version: 0.1.0
|
|
171
|
+
mode: advisory
|
|
172
|
+
owner: appsec
|
|
173
|
+
description: A minimal rules pack to start from.
|
|
174
|
+
|
|
175
|
+
rules:
|
|
176
|
+
- id: APPSEC-EXAMPLE-001
|
|
177
|
+
title: Require controlled error handling
|
|
178
|
+
description: Verify that invalid user input returns controlled errors without stack traces.
|
|
179
|
+
severity: medium
|
|
180
|
+
category: configuration
|
|
181
|
+
status: enabled
|
|
182
|
+
enforcement: advisory
|
|
183
|
+
targets:
|
|
184
|
+
- api
|
|
185
|
+
mappings:
|
|
186
|
+
owasp_asvs:
|
|
187
|
+
- V14.4
|
|
188
|
+
owasp_api_top_10_2023:
|
|
189
|
+
- API8:2023
|
|
190
|
+
cwe:
|
|
191
|
+
- CWE-209
|
|
192
|
+
nist_ssdf:
|
|
193
|
+
- PW.7
|
|
194
|
+
evidence:
|
|
195
|
+
required:
|
|
196
|
+
- Error handling path returns a documented response shape.
|
|
197
|
+
signals:
|
|
198
|
+
- Negative tests assert controlled error messages for invalid input.
|
|
199
|
+
match:
|
|
200
|
+
type: review
|
|
201
|
+
includes:
|
|
202
|
+
- API handlers that process untrusted input.
|
|
203
|
+
excludes:
|
|
204
|
+
- Local developer-only scripts that are not shipped.
|
|
205
|
+
remediation:
|
|
206
|
+
guidance: Replace raw exception output with a controlled error contract.
|
|
207
|
+
validation:
|
|
208
|
+
- Run negative tests for invalid input and malformed requests.
|
|
209
|
+
exceptions:
|
|
210
|
+
allowed: true
|
|
211
|
+
max_days: 30
|
|
212
|
+
required_fields:
|
|
213
|
+
- owner
|
|
214
|
+
- justification
|
|
215
|
+
- expires_at
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Note the shapes that are easy to guess wrong: `evidence` and `match` are objects (not
|
|
219
|
+
lists), `remediation` needs `guidance`, and `required_fields` accepts only `owner`,
|
|
220
|
+
`justification`, `expires_at`, `compensating_control`, and `validation_plan`.
|
|
221
|
+
|
|
117
222
|
## Usage
|
|
118
223
|
|
|
224
|
+
The examples below use `rules/appsec-baseline.yaml`, which exists in **this repository**.
|
|
225
|
+
If you installed from PyPI, point the CLI at your own rules file or directory instead.
|
|
226
|
+
|
|
119
227
|
Validate the baseline rules pack:
|
|
120
228
|
|
|
121
229
|
```bash
|
|
@@ -172,9 +280,9 @@ code is non-zero when validation fails, matching the text output.
|
|
|
172
280
|
|
|
173
281
|
## Use It In Your CI
|
|
174
282
|
|
|
175
|
-
`examples/README.md` contains a GitHub Actions template for installing the pack
|
|
176
|
-
validating a rules directory, and failing the build on errors (and optionally
|
|
177
|
-
warnings). Pin the
|
|
283
|
+
`examples/README.md` contains a GitHub Actions template for installing the pack from
|
|
284
|
+
PyPI, validating a rules directory, and failing the build on errors (and optionally
|
|
285
|
+
warnings). Pin the install to a reviewed release version before enabling it as a
|
|
178
286
|
quality gate.
|
|
179
287
|
|
|
180
288
|
## Rule Pack Model
|
|
@@ -80,5 +80,7 @@ branch = true
|
|
|
80
80
|
source = ["appsec_rules_pack"]
|
|
81
81
|
|
|
82
82
|
[tool.coverage.report]
|
|
83
|
-
|
|
83
|
+
# Raised from 90 once actual coverage settled above 97%. A gate far below the real
|
|
84
|
+
# number permits a silent regression the size of the gap.
|
|
85
|
+
fail_under = 95
|
|
84
86
|
show_missing = true
|
|
@@ -111,6 +111,23 @@ def _format_file_issue(base_path: Path, file_path: Path, issue: ValidationIssue)
|
|
|
111
111
|
return f"{_display_path(base_path, file_path)}: {_format_issue(issue)}"
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
def _write_document(output: Path, document: str) -> None:
|
|
115
|
+
"""Write a derived document, reporting write failures as an actionable CLI error.
|
|
116
|
+
|
|
117
|
+
Pointing ``--output`` at an existing directory, or at any path that cannot be
|
|
118
|
+
written, used to escape as a raw Python traceback (``PermissionError`` on Windows,
|
|
119
|
+
``IsADirectoryError`` on POSIX). The caller keeps its own success message.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
124
|
+
output.write_text(document, encoding="utf-8", newline="\n")
|
|
125
|
+
except OSError as error:
|
|
126
|
+
reason = error.strerror or str(error)
|
|
127
|
+
typer.echo(f"Write failed: cannot write to {output}: {reason}.", err=True)
|
|
128
|
+
raise typer.Exit(code=1) from error
|
|
129
|
+
|
|
130
|
+
|
|
114
131
|
def _iter_rule_files(path: Path) -> tuple[Path, ...]:
|
|
115
132
|
if path.is_file():
|
|
116
133
|
return (path,)
|
|
@@ -273,8 +290,7 @@ def export_index(
|
|
|
273
290
|
document = json.dumps(index, indent=2) + "\n"
|
|
274
291
|
|
|
275
292
|
if output is not None:
|
|
276
|
-
output
|
|
277
|
-
output.write_text(document, encoding="utf-8")
|
|
293
|
+
_write_document(output, document)
|
|
278
294
|
typer.echo(f"Wrote index for {_plural(len(rule_files), 'file', 'files')} to {output}.")
|
|
279
295
|
return
|
|
280
296
|
|
|
@@ -302,8 +318,7 @@ def export_semgrep(
|
|
|
302
318
|
document = SEMGREP_HEADER + body
|
|
303
319
|
|
|
304
320
|
if output is not None:
|
|
305
|
-
output
|
|
306
|
-
output.write_text(document, encoding="utf-8")
|
|
321
|
+
_write_document(output, document)
|
|
307
322
|
typer.echo(
|
|
308
323
|
f"Wrote Semgrep scaffold for {_plural(len(rule_files), 'file', 'files')} to {output}."
|
|
309
324
|
)
|
|
@@ -332,8 +347,7 @@ def export_sarif(
|
|
|
332
347
|
document = json.dumps(sarif, indent=2) + "\n"
|
|
333
348
|
|
|
334
349
|
if output is not None:
|
|
335
|
-
output
|
|
336
|
-
output.write_text(document, encoding="utf-8")
|
|
350
|
+
_write_document(output, document)
|
|
337
351
|
typer.echo(
|
|
338
352
|
f"Wrote SARIF rule-catalog for {_plural(len(rule_files), 'file', 'files')} to {output}."
|
|
339
353
|
)
|
|
@@ -360,11 +374,21 @@ def report_coverage(
|
|
|
360
374
|
|
|
361
375
|
coverage = build_coverage_from_files(list(rule_files))
|
|
362
376
|
|
|
377
|
+
if coverage["rules"] == 0:
|
|
378
|
+
# The report is derived without schema validation, so a pack whose `rules` key is
|
|
379
|
+
# missing or malformed still produces a clean-looking 0/0 report. Say so on stderr
|
|
380
|
+
# rather than letting an empty report read as a healthy one. The exit code stays 0:
|
|
381
|
+
# reporting is derivation, and `validate` is what judges pack structure.
|
|
382
|
+
typer.echo(
|
|
383
|
+
f"Warning: no rules found in {rules_path}; the coverage report is empty. "
|
|
384
|
+
"Run `validate` to check the pack structure.",
|
|
385
|
+
err=True,
|
|
386
|
+
)
|
|
387
|
+
|
|
363
388
|
if output_format is OutputFormat.json:
|
|
364
389
|
document = json.dumps(coverage, indent=2) + "\n"
|
|
365
390
|
if output is not None:
|
|
366
|
-
output
|
|
367
|
-
output.write_text(document, encoding="utf-8")
|
|
391
|
+
_write_document(output, document)
|
|
368
392
|
typer.echo(f"Wrote coverage report to {output}.")
|
|
369
393
|
return
|
|
370
394
|
typer.echo(document, nl=False)
|
|
@@ -181,14 +181,18 @@ def validate_rules_payload(payload: Any, *, require_examples: bool = False) -> V
|
|
|
181
181
|
schema = _load_schema()
|
|
182
182
|
validator = jsonschema.Draft202012Validator(schema)
|
|
183
183
|
|
|
184
|
+
# jsonschema reports one error per missing required property, but the rendered
|
|
185
|
+
# message names every missing field at that location. Three missing fields therefore
|
|
186
|
+
# produced three byte-identical lines, and inflated the reported error count. An
|
|
187
|
+
# identical level+path+message carries no extra information, so it is dropped.
|
|
188
|
+
seen_schema_issues: set[tuple[str, tuple[Any, ...]]] = set()
|
|
184
189
|
for error in sorted(validator.iter_errors(payload), key=lambda item: list(item.path)):
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)
|
|
190
|
+
message = _schema_issue_message(error)
|
|
191
|
+
path = tuple(error.absolute_path)
|
|
192
|
+
if (message, path) in seen_schema_issues:
|
|
193
|
+
continue
|
|
194
|
+
seen_schema_issues.add((message, path))
|
|
195
|
+
issues.append(ValidationIssue(level="error", message=message, path=path))
|
|
192
196
|
|
|
193
197
|
if isinstance(payload, dict):
|
|
194
198
|
issues.extend(_semantic_issues(payload, require_examples=require_examples))
|