icsprio 1.0.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 (57) hide show
  1. icsprio-1.0.0/.gitignore +20 -0
  2. icsprio-1.0.0/AUTHORS.json +27 -0
  3. icsprio-1.0.0/LICENSE +30 -0
  4. icsprio-1.0.0/LICENSE-DATA +20 -0
  5. icsprio-1.0.0/PKG-INFO +191 -0
  6. icsprio-1.0.0/README.md +146 -0
  7. icsprio-1.0.0/code/01_fetch.py +29 -0
  8. icsprio-1.0.0/code/02_build.py +31 -0
  9. icsprio-1.0.0/code/03_qa.py +36 -0
  10. icsprio-1.0.0/docs/ATTACK_MAPPING.md +60 -0
  11. icsprio-1.0.0/docs/CODEBOOK.md +44 -0
  12. icsprio-1.0.0/docs/LIMITATIONS.md +67 -0
  13. icsprio-1.0.0/docs/NEXT_STEPS.md +31 -0
  14. icsprio-1.0.0/docs/PUBLISH_GUIDE.md +134 -0
  15. icsprio-1.0.0/docs/SCORING.md +83 -0
  16. icsprio-1.0.0/docs/VERIFY_CHECKLIST.md +73 -0
  17. icsprio-1.0.0/icsprio/__init__.py +8 -0
  18. icsprio-1.0.0/icsprio/__main__.py +4 -0
  19. icsprio-1.0.0/icsprio/attack_mapping.py +57 -0
  20. icsprio-1.0.0/icsprio/cli.py +117 -0
  21. icsprio-1.0.0/icsprio/config.py +97 -0
  22. icsprio-1.0.0/icsprio/http.py +50 -0
  23. icsprio-1.0.0/icsprio/io_utils.py +39 -0
  24. icsprio-1.0.0/icsprio/join.py +123 -0
  25. icsprio-1.0.0/icsprio/pipeline.py +126 -0
  26. icsprio-1.0.0/icsprio/provenance.py +45 -0
  27. icsprio-1.0.0/icsprio/qa.py +142 -0
  28. icsprio-1.0.0/icsprio/reference_data/cwe_to_attack_ics.csv +46 -0
  29. icsprio-1.0.0/icsprio/scoring.py +105 -0
  30. icsprio-1.0.0/icsprio/sources/__init__.py +8 -0
  31. icsprio-1.0.0/icsprio/sources/attack_ics.py +85 -0
  32. icsprio-1.0.0/icsprio/sources/cisa_ics.py +208 -0
  33. icsprio-1.0.0/icsprio/sources/epss.py +85 -0
  34. icsprio-1.0.0/icsprio/sources/kev.py +73 -0
  35. icsprio-1.0.0/icsprio/sources/vulnrichment.py +152 -0
  36. icsprio-1.0.0/paper/paper.bib +24 -0
  37. icsprio-1.0.0/paper/paper.md +110 -0
  38. icsprio-1.0.0/pyproject.toml +82 -0
  39. icsprio-1.0.0/tests/conftest.py +36 -0
  40. icsprio-1.0.0/tests/fixtures/README.md +14 -0
  41. icsprio-1.0.0/tests/fixtures/attack_ics_sample.json +35 -0
  42. icsprio-1.0.0/tests/fixtures/csaf_advisory_sample.json +35 -0
  43. icsprio-1.0.0/tests/fixtures/epss_sample.json +14 -0
  44. icsprio-1.0.0/tests/fixtures/kev_sample.json +34 -0
  45. icsprio-1.0.0/tests/fixtures/vulnrichment_cve_sample.json +49 -0
  46. icsprio-1.0.0/tests/test_attack_ics.py +18 -0
  47. icsprio-1.0.0/tests/test_attack_mapping.py +32 -0
  48. icsprio-1.0.0/tests/test_cisa_ics.py +33 -0
  49. icsprio-1.0.0/tests/test_cli.py +46 -0
  50. icsprio-1.0.0/tests/test_epss.py +18 -0
  51. icsprio-1.0.0/tests/test_join.py +60 -0
  52. icsprio-1.0.0/tests/test_kev.py +14 -0
  53. icsprio-1.0.0/tests/test_pipeline.py +54 -0
  54. icsprio-1.0.0/tests/test_provenance.py +33 -0
  55. icsprio-1.0.0/tests/test_qa.py +48 -0
  56. icsprio-1.0.0/tests/test_scoring.py +59 -0
  57. icsprio-1.0.0/tests/test_vulnrichment.py +26 -0
@@ -0,0 +1,20 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .venv/
11
+ venv/
12
+ .env
13
+ *.env
14
+ data/raw/*
15
+ !data/raw/.gitkeep
16
+ !data/raw/PROVENANCE.txt
17
+ .DS_Store
18
+ *.log
19
+ .coverage
20
+ htmlcov/
@@ -0,0 +1,27 @@
1
+ {
2
+ "_comment": "Author block for icsprio. Every document, citation file, Zenodo record, manuscript author list, and commit identity reads from here.",
3
+ "authors": [
4
+ {
5
+ "name": "Friday Ogochukwu Ikwuogu",
6
+ "family": "Ikwuogu",
7
+ "given": "Friday Ogochukwu",
8
+ "orcid": "0009-0009-2222-1318",
9
+ "email": "Friday.ikwuogu@gmail.com",
10
+ "affiliation": "Independent Researcher, Odessa, Texas, USA",
11
+ "roles": ["Conceptualization", "Methodology", "Software", "Data curation", "Validation", "Writing - original draft"],
12
+ "corresponding": true
13
+ }
14
+ ],
15
+ "collaborators": [
16
+ {
17
+ "name": "David Mike-Ewewie",
18
+ "family": "Mike-Ewewie",
19
+ "given": "David",
20
+ "orcid": "",
21
+ "email": "mike_d63291@utpb.edu",
22
+ "affiliation": "Computer Science Department, University of Texas Permian Basin, Odessa, Texas, USA",
23
+ "roles": ["Validation", "Writing - review and editing"],
24
+ "corresponding": false
25
+ }
26
+ ]
27
+ }
icsprio-1.0.0/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Friday Ogochukwu Ikwuogu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This license covers the icsprio SOURCE CODE only. The joined/derived data
26
+ files produced by running the pipeline (data/processed/*) are licensed
27
+ separately under CC BY 4.0 — see LICENSE-DATA — because they are derived from
28
+ third-party sources (CISA, FIRST.org, MITRE, the CVE Program) that require
29
+ attribution. Those sources' own terms of use also continue to apply; see
30
+ README.md § Sources.
@@ -0,0 +1,20 @@
1
+ Creative Commons Attribution 4.0 International (CC BY 4.0)
2
+
3
+ The joined/derived data files produced by icsprio (everything under
4
+ data/processed/, including icsprio_joined.csv and icsprio_joined.parquet)
5
+ are licensed under CC BY 4.0: https://creativecommons.org/licenses/by/4.0/
6
+
7
+ Attribution required when redistributing or building on these outputs:
8
+
9
+ Derived from icsprio (Ikwuogu & Mike-Ewewie), joining data from:
10
+ - CISA ICS Advisories (Cybersecurity and Infrastructure Security Agency,
11
+ U.S. Government work, public domain)
12
+ - CISA Known Exploited Vulnerabilities (KEV) Catalog (public domain)
13
+ - EPSS scores, FIRST.org, Inc. (see https://www.first.org/epss/ for terms)
14
+ - CISA Vulnrichment (https://github.com/cisagov/vulnrichment, CC0-1.0)
15
+ - MITRE ATT&CK for ICS (https://attack.mitre.org, subject to the ATT&CK
16
+ Terms of Use: https://attack.mitre.org/resources/terms-of-use/)
17
+
18
+ Each underlying source's own license/terms continue to apply to that
19
+ source's contribution to the joined output; CC BY 4.0 governs icsprio's own
20
+ joining, scoring, and derived columns.
icsprio-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.5
2
+ Name: icsprio
3
+ Version: 1.0.0
4
+ Summary: Fetch, join, and prioritize ICS vulnerability intelligence from CISA ICS Advisories, KEV, EPSS, Vulnrichment, and ATT&CK for ICS.
5
+ Project-URL: Homepage, https://github.com/foikwuogu/icsprio
6
+ Project-URL: Repository, https://github.com/foikwuogu/icsprio
7
+ Project-URL: Issues, https://github.com/foikwuogu/icsprio/issues
8
+ Project-URL: Documentation, https://github.com/foikwuogu/icsprio/tree/main/docs
9
+ Author-email: Friday Ogochukwu Ikwuogu <Friday.ikwuogu@gmail.com>
10
+ Maintainer-email: Friday Ogochukwu Ikwuogu <Friday.ikwuogu@gmail.com>
11
+ License: MIT
12
+ License-File: AUTHORS.json
13
+ License-File: LICENSE
14
+ License-File: LICENSE-DATA
15
+ Keywords: attack,cisa,critical-infrastructure,cybersecurity,epss,ics,kev,ot,scada,vulnerability-management,vulnrichment
16
+ Classifier: Development Status :: 5 - Production/Stable
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
27
+ Classifier: Topic :: Security
28
+ Requires-Python: >=3.9
29
+ Requires-Dist: click>=8.1
30
+ Requires-Dist: pandas>=2.0
31
+ Requires-Dist: pyyaml>=6.0
32
+ Requires-Dist: requests>=2.31
33
+ Provides-Extra: dashboard
34
+ Requires-Dist: streamlit>=1.57; extra == 'dashboard'
35
+ Provides-Extra: dev
36
+ Requires-Dist: build>=1.0; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
38
+ Requires-Dist: pytest>=7.4; extra == 'dev'
39
+ Requires-Dist: responses>=0.24; extra == 'dev'
40
+ Requires-Dist: ruff>=0.4; extra == 'dev'
41
+ Requires-Dist: twine>=5.0; extra == 'dev'
42
+ Provides-Extra: parquet
43
+ Requires-Dist: pyarrow>=14.0; extra == 'parquet'
44
+ Description-Content-Type: text/markdown
45
+
46
+ # icsprio
47
+
48
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22732040.svg)](https://doi.org/10.5281/zenodo.22732040)
49
+
50
+ **Status:** v1.0.0 (verified — see [Verification](#verification)) | **Maintainer:** Friday Ogochukwu Ikwuogu, [ORCID 0009-0009-2222-1318](https://orcid.org/0009-0009-2222-1318) | **License:** code [MIT](LICENSE), joined data [CC BY 4.0](LICENSE-DATA)
51
+
52
+ icsprio is an open Python package that fetches, joins, and prioritizes ICS
53
+ (industrial control systems) vulnerability intelligence from five public
54
+ sources — CISA ICS Advisories, the CISA Known Exploited Vulnerabilities
55
+ (KEV) catalog, FIRST.org's EPSS, CISA's Vulnrichment SSVC/CVSS enrichment,
56
+ and MITRE ATT&CK for ICS — into one deterministic, reproducible table with
57
+ an OT-relevant priority score. It is built for operators, researchers, and
58
+ auditors who need a defensible, explainable answer to "which of these ICS
59
+ advisories should we act on first," instead of five browser tabs and a
60
+ spreadsheet.
61
+
62
+ Every number icsprio produces is reproducible: given the same fetched data
63
+ and the same scoring weights, two runs never disagree, and every weight and
64
+ mapping decision is documented and author-reviewed, not a black box.
65
+
66
+ ## What is here
67
+
68
+ ```
69
+ icsprio/ the installable package (fetchers, join, scoring, QA, CLI)
70
+ icsprio/reference_data/ cwe_to_attack_ics.csv — the author-reviewed CWE->ATT&CK crosswalk
71
+ code/ numbered scripts in run order (thin wrappers over icsprio/)
72
+ data/raw/ inputs as fetched, with PROVENANCE.txt (created by `fetch`)
73
+ data/processed/ joined table, qa_report.txt, stats.json (created by `build`)
74
+ docs/ CODEBOOK, SCORING, ATTACK_MAPPING, LIMITATIONS, VERIFY_CHECKLIST, NEXT_STEPS
75
+ tests/ offline pytest suite (fixtures only, no network calls)
76
+ paper/ JOSS-format paper draft (prepared, not yet submitted)
77
+ ```
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ pip install icsprio # once published to PyPI
83
+ # or, from a clone:
84
+ pip install -e ".[dev,parquet]"
85
+ ```
86
+
87
+ Requires Python 3.9+. Runtime dependencies are deliberately minimal:
88
+ `requests`, `pandas`, `click`, `PyYAML`.
89
+
90
+ ## Run it
91
+
92
+ ```bash
93
+ icsprio run # fetch + join + score + QA in one call
94
+ icsprio report # print the QA report
95
+ ```
96
+
97
+ or, to re-score already-fetched data (e.g. after editing `docs/SCORING.md`'s
98
+ weights) without hitting the network again:
99
+
100
+ ```bash
101
+ icsprio fetch --since-year 2023
102
+ icsprio build --weights-file my_weights.yaml
103
+ ```
104
+
105
+ or the numbered scripts, if you'd rather not install the CLI:
106
+
107
+ ```bash
108
+ python code/01_fetch.py --since-year 2023
109
+ python code/02_build.py
110
+ python code/03_qa.py
111
+ ```
112
+
113
+ A stranger should be able to run any of the above from this README alone
114
+ and get `data/processed/icsprio_joined.csv` and `qa_report.txt`.
115
+
116
+ ### Default scope
117
+
118
+ By default, `fetch` pulls ICS advisories from the last 3 years
119
+ (`--since-year <current year - 3>`), not the full historical archive. This
120
+ is a deliberate default, not a limitation of the data — see
121
+ [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for why, and pass
122
+ `--since-year 2010` (or any earlier year) for a full historical pull. Set
123
+ the `GITHUB_TOKEN` environment variable to raise GitHub's API rate limit
124
+ for larger pulls.
125
+
126
+ ## Sources
127
+
128
+ | Source | What it provides | Vintage | License / terms | Accessed |
129
+ |---|---|---|---|---|
130
+ | [CISA ICS Advisories (CSAF 2.0)](https://www.cisa.gov/news-events/ics-advisories), via [cisagov/CSAF](https://github.com/cisagov/CSAF) | Advisory metadata, vendor/product, CVEs | ongoing | U.S. Government work, public domain | 2026-09-13 |
131
+ | [CISA KEV catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) | Confirmed real-world exploitation | updated continuously | U.S. Government work, public domain | 2026-09-13 |
132
+ | [FIRST.org EPSS](https://www.first.org/epss/) | Exploitation-likelihood score | daily, back to 2021-04-14 | FIRST.org terms of use | 2026-09-13 |
133
+ | [CISA Vulnrichment](https://github.com/cisagov/vulnrichment) | SSVC decision points, CVSS, CWE | ongoing (partial CVE coverage) | CC0-1.0 | 2026-09-13 |
134
+ | [MITRE ATT&CK for ICS](https://github.com/mitre-attack/attack-stix-data) | Adversary technique catalog | versioned releases | ATT&CK Terms of Use | 2026-09-13 |
135
+
136
+ ## How prioritization works
137
+
138
+ `priority_score` (0-100) is a deterministic weighted combination of KEV
139
+ status, EPSS percentile, and Vulnrichment's SSVC decision points. The exact
140
+ weights and the rationale for each are in [docs/SCORING.md](docs/SCORING.md)
141
+ — **read that before trusting or citing a score**; it is icsprio's own
142
+ analytic contribution, not a value CISA or FIRST publishes. `attack_technique_ids`
143
+ comes from a documented, author-reviewed CWE-to-ATT&CK-for-ICS heuristic
144
+ crosswalk (no authoritative CVE-to-technique mapping exists) — see
145
+ [docs/ATTACK_MAPPING.md](docs/ATTACK_MAPPING.md).
146
+
147
+ ## Headline numbers
148
+
149
+ From `data/processed/stats.json`, generated by a real `icsprio run` against
150
+ live sources (2026-09-13, `--since-year 2023`):
151
+
152
+ - 1,685 advisories fetched, 8,653 (advisory, CVE) rows, 7,107 unique CVEs
153
+ - Join match rates: KEV 1.0%, EPSS 99.7%, Vulnrichment 78.9%, ATT&CK-for-ICS mapped 5.9%
154
+ - Priority bands: Critical 83, High 37, Medium 1,978, Low 6,555
155
+ - 0 sanity-check problems
156
+
157
+ Re-run `icsprio run` yourself and these numbers regenerate from your own
158
+ live fetch — never take them as static.
159
+
160
+ ## Verification
161
+
162
+ This release has cleared the author verification gate
163
+ ([docs/VERIFY_CHECKLIST.md](docs/VERIFY_CHECKLIST.md)): a live `icsprio run`
164
+ against real sources, the full `pytest` suite passing, and a hand review of
165
+ `docs/SCORING.md`'s weights and `icsprio/reference_data/cwe_to_attack_ics.csv`'s
166
+ crosswalk.
167
+
168
+ See [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for what remains provisional
169
+ by design (e.g. the crosswalk is a documented heuristic, not a MITRE
170
+ mapping) rather than unfinished.
171
+
172
+ ## Publishing
173
+
174
+ Published on GitHub and archived on Zenodo
175
+ ([10.5281/zenodo.22732040](https://doi.org/10.5281/zenodo.22732040)); see
176
+ [docs/PUBLISH_GUIDE.md](docs/PUBLISH_GUIDE.md) for PyPI steps.
177
+
178
+ ## Citation
179
+
180
+ See [CITATION.cff](CITATION.cff). DOI:
181
+ [10.5281/zenodo.22732040](https://doi.org/10.5281/zenodo.22732040).
182
+
183
+ ## Contributing
184
+
185
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
186
+
187
+ ## Authors
188
+
189
+ See [AUTHORS.json](AUTHORS.json) — the single source of truth for
190
+ authorship across this README, CITATION.cff, the JOSS paper, and Zenodo
191
+ metadata.
@@ -0,0 +1,146 @@
1
+ # icsprio
2
+
3
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22732040.svg)](https://doi.org/10.5281/zenodo.22732040)
4
+
5
+ **Status:** v1.0.0 (verified — see [Verification](#verification)) | **Maintainer:** Friday Ogochukwu Ikwuogu, [ORCID 0009-0009-2222-1318](https://orcid.org/0009-0009-2222-1318) | **License:** code [MIT](LICENSE), joined data [CC BY 4.0](LICENSE-DATA)
6
+
7
+ icsprio is an open Python package that fetches, joins, and prioritizes ICS
8
+ (industrial control systems) vulnerability intelligence from five public
9
+ sources — CISA ICS Advisories, the CISA Known Exploited Vulnerabilities
10
+ (KEV) catalog, FIRST.org's EPSS, CISA's Vulnrichment SSVC/CVSS enrichment,
11
+ and MITRE ATT&CK for ICS — into one deterministic, reproducible table with
12
+ an OT-relevant priority score. It is built for operators, researchers, and
13
+ auditors who need a defensible, explainable answer to "which of these ICS
14
+ advisories should we act on first," instead of five browser tabs and a
15
+ spreadsheet.
16
+
17
+ Every number icsprio produces is reproducible: given the same fetched data
18
+ and the same scoring weights, two runs never disagree, and every weight and
19
+ mapping decision is documented and author-reviewed, not a black box.
20
+
21
+ ## What is here
22
+
23
+ ```
24
+ icsprio/ the installable package (fetchers, join, scoring, QA, CLI)
25
+ icsprio/reference_data/ cwe_to_attack_ics.csv — the author-reviewed CWE->ATT&CK crosswalk
26
+ code/ numbered scripts in run order (thin wrappers over icsprio/)
27
+ data/raw/ inputs as fetched, with PROVENANCE.txt (created by `fetch`)
28
+ data/processed/ joined table, qa_report.txt, stats.json (created by `build`)
29
+ docs/ CODEBOOK, SCORING, ATTACK_MAPPING, LIMITATIONS, VERIFY_CHECKLIST, NEXT_STEPS
30
+ tests/ offline pytest suite (fixtures only, no network calls)
31
+ paper/ JOSS-format paper draft (prepared, not yet submitted)
32
+ ```
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install icsprio # once published to PyPI
38
+ # or, from a clone:
39
+ pip install -e ".[dev,parquet]"
40
+ ```
41
+
42
+ Requires Python 3.9+. Runtime dependencies are deliberately minimal:
43
+ `requests`, `pandas`, `click`, `PyYAML`.
44
+
45
+ ## Run it
46
+
47
+ ```bash
48
+ icsprio run # fetch + join + score + QA in one call
49
+ icsprio report # print the QA report
50
+ ```
51
+
52
+ or, to re-score already-fetched data (e.g. after editing `docs/SCORING.md`'s
53
+ weights) without hitting the network again:
54
+
55
+ ```bash
56
+ icsprio fetch --since-year 2023
57
+ icsprio build --weights-file my_weights.yaml
58
+ ```
59
+
60
+ or the numbered scripts, if you'd rather not install the CLI:
61
+
62
+ ```bash
63
+ python code/01_fetch.py --since-year 2023
64
+ python code/02_build.py
65
+ python code/03_qa.py
66
+ ```
67
+
68
+ A stranger should be able to run any of the above from this README alone
69
+ and get `data/processed/icsprio_joined.csv` and `qa_report.txt`.
70
+
71
+ ### Default scope
72
+
73
+ By default, `fetch` pulls ICS advisories from the last 3 years
74
+ (`--since-year <current year - 3>`), not the full historical archive. This
75
+ is a deliberate default, not a limitation of the data — see
76
+ [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for why, and pass
77
+ `--since-year 2010` (or any earlier year) for a full historical pull. Set
78
+ the `GITHUB_TOKEN` environment variable to raise GitHub's API rate limit
79
+ for larger pulls.
80
+
81
+ ## Sources
82
+
83
+ | Source | What it provides | Vintage | License / terms | Accessed |
84
+ |---|---|---|---|---|
85
+ | [CISA ICS Advisories (CSAF 2.0)](https://www.cisa.gov/news-events/ics-advisories), via [cisagov/CSAF](https://github.com/cisagov/CSAF) | Advisory metadata, vendor/product, CVEs | ongoing | U.S. Government work, public domain | 2026-09-13 |
86
+ | [CISA KEV catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) | Confirmed real-world exploitation | updated continuously | U.S. Government work, public domain | 2026-09-13 |
87
+ | [FIRST.org EPSS](https://www.first.org/epss/) | Exploitation-likelihood score | daily, back to 2021-04-14 | FIRST.org terms of use | 2026-09-13 |
88
+ | [CISA Vulnrichment](https://github.com/cisagov/vulnrichment) | SSVC decision points, CVSS, CWE | ongoing (partial CVE coverage) | CC0-1.0 | 2026-09-13 |
89
+ | [MITRE ATT&CK for ICS](https://github.com/mitre-attack/attack-stix-data) | Adversary technique catalog | versioned releases | ATT&CK Terms of Use | 2026-09-13 |
90
+
91
+ ## How prioritization works
92
+
93
+ `priority_score` (0-100) is a deterministic weighted combination of KEV
94
+ status, EPSS percentile, and Vulnrichment's SSVC decision points. The exact
95
+ weights and the rationale for each are in [docs/SCORING.md](docs/SCORING.md)
96
+ — **read that before trusting or citing a score**; it is icsprio's own
97
+ analytic contribution, not a value CISA or FIRST publishes. `attack_technique_ids`
98
+ comes from a documented, author-reviewed CWE-to-ATT&CK-for-ICS heuristic
99
+ crosswalk (no authoritative CVE-to-technique mapping exists) — see
100
+ [docs/ATTACK_MAPPING.md](docs/ATTACK_MAPPING.md).
101
+
102
+ ## Headline numbers
103
+
104
+ From `data/processed/stats.json`, generated by a real `icsprio run` against
105
+ live sources (2026-09-13, `--since-year 2023`):
106
+
107
+ - 1,685 advisories fetched, 8,653 (advisory, CVE) rows, 7,107 unique CVEs
108
+ - Join match rates: KEV 1.0%, EPSS 99.7%, Vulnrichment 78.9%, ATT&CK-for-ICS mapped 5.9%
109
+ - Priority bands: Critical 83, High 37, Medium 1,978, Low 6,555
110
+ - 0 sanity-check problems
111
+
112
+ Re-run `icsprio run` yourself and these numbers regenerate from your own
113
+ live fetch — never take them as static.
114
+
115
+ ## Verification
116
+
117
+ This release has cleared the author verification gate
118
+ ([docs/VERIFY_CHECKLIST.md](docs/VERIFY_CHECKLIST.md)): a live `icsprio run`
119
+ against real sources, the full `pytest` suite passing, and a hand review of
120
+ `docs/SCORING.md`'s weights and `icsprio/reference_data/cwe_to_attack_ics.csv`'s
121
+ crosswalk.
122
+
123
+ See [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for what remains provisional
124
+ by design (e.g. the crosswalk is a documented heuristic, not a MITRE
125
+ mapping) rather than unfinished.
126
+
127
+ ## Publishing
128
+
129
+ Published on GitHub and archived on Zenodo
130
+ ([10.5281/zenodo.22732040](https://doi.org/10.5281/zenodo.22732040)); see
131
+ [docs/PUBLISH_GUIDE.md](docs/PUBLISH_GUIDE.md) for PyPI steps.
132
+
133
+ ## Citation
134
+
135
+ See [CITATION.cff](CITATION.cff). DOI:
136
+ [10.5281/zenodo.22732040](https://doi.org/10.5281/zenodo.22732040).
137
+
138
+ ## Contributing
139
+
140
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
141
+
142
+ ## Authors
143
+
144
+ See [AUTHORS.json](AUTHORS.json) — the single source of truth for
145
+ authorship across this README, CITATION.cff, the JOSS paper, and Zenodo
146
+ metadata.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ """Fetch every source and write data/raw/_fetch_manifest.json.
3
+
4
+ Equivalent to `icsprio fetch`; kept as a numbered script so the repository
5
+ matches the project's standard run-in-order layout even for someone who
6
+ hasn't installed the package as a CLI.
7
+
8
+ Usage: python code/01_fetch.py [--since-year YYYY]
9
+ """
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
15
+
16
+ from icsprio import pipeline # noqa: E402
17
+
18
+
19
+ def main():
20
+ ap = argparse.ArgumentParser()
21
+ ap.add_argument("--since-year", type=int, default=None)
22
+ args = ap.parse_args()
23
+ manifest = pipeline.run_fetch(since_year=args.since_year)
24
+ for stage, count in manifest["stage_counts"].items():
25
+ print(f"{stage}: {count}")
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ """Join, score, and write data/processed/icsprio_joined.{csv,parquet}.
3
+
4
+ Usage: python code/02_build.py [--weights-file weights.yaml]
5
+ """
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
11
+
12
+ from icsprio import io_utils, pipeline # noqa: E402
13
+ from icsprio.cli import _load_weights # noqa: E402
14
+
15
+
16
+ def main():
17
+ ap = argparse.ArgumentParser()
18
+ ap.add_argument("--weights-file", default=None)
19
+ args = ap.parse_args()
20
+
21
+ manifest = pipeline.load_manifest()
22
+ weights = _load_weights(args.weights_file)
23
+ df = pipeline.run_build(manifest, weights=weights)
24
+
25
+ io_utils.export_csv(df, "data/processed/icsprio_joined.csv")
26
+ io_utils.export_parquet(df, "data/processed/icsprio_joined.parquet")
27
+ print(f"Wrote {len(df)} rows to data/processed/icsprio_joined.csv")
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+ """Generate data/processed/qa_report.txt and stats.json from the built table.
3
+
4
+ Usage: python code/03_qa.py [--final]
5
+ """
6
+ import argparse
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
11
+
12
+ import pandas as pd # noqa: E402
13
+
14
+ from icsprio import io_utils, pipeline # noqa: E402
15
+
16
+
17
+ def main():
18
+ ap = argparse.ArgumentParser()
19
+ ap.add_argument("--final", action="store_true")
20
+ args = ap.parse_args()
21
+
22
+ manifest = pipeline.load_manifest()
23
+ df = pd.read_csv("data/processed/icsprio_joined.csv")
24
+ # list-typed columns round-trip through CSV as "a; b" strings; restore lists
25
+ for col in ("vendors", "products", "cwes", "attack_technique_ids", "attack_technique_names"):
26
+ df[col] = df[col].fillna("").apply(lambda v: [s for s in str(v).split("; ") if s])
27
+
28
+ stats, report_text = pipeline.run_qa(df, manifest["stage_counts"], draft=not args.final)
29
+ io_utils.write_stats_json(stats, "data/processed/stats.json")
30
+ with open("data/processed/qa_report.txt", "w", encoding="utf-8") as f:
31
+ f.write(report_text)
32
+ print(report_text)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
@@ -0,0 +1,60 @@
1
+ # CWE -> ATT&CK-for-ICS crosswalk methodology
2
+
3
+ **Author-reviewed heuristic:** This crosswalk is icsprio's own heuristic,
4
+ authored for this project. It is not a MITRE, CISA, or CVE Program mapping;
5
+ every row in `icsprio/reference_data/cwe_to_attack_ics.csv` has been reviewed
6
+ by the author.
7
+
8
+ ## Why a crosswalk is needed at all
9
+
10
+ No authoritative mapping from a CVE (or its CWE weakness class) to ATT&CK
11
+ for ICS techniques exists. ATT&CK for ICS catalogs *adversary behavior after
12
+ an initial foothold or during an intrusion* (e.g. "Modify Parameter," "Denial
13
+ of Service"); CWE catalogs *root-cause weakness types* in software (e.g.
14
+ "Out-of-bounds Write," "Missing Authentication"). They describe different
15
+ things at different points in an intrusion, so there is no clean 1:1
16
+ mapping — this crosswalk is a documented, reviewable judgment call about
17
+ which techniques an adversary would plausibly use *given* a vulnerability of
18
+ a certain weakness class, not a claim that exploiting CWE-X always produces
19
+ technique T.
20
+
21
+ ## Method
22
+
23
+ 1. For each CWE commonly seen in ICS advisories, ask: if an adversary
24
+ exploited a vulnerability of this weakness class, what's the most direct
25
+ ATT&CK-for-ICS technique describing what they could then do?
26
+ 2. Assign one or more mapped techniques, each with:
27
+ - `confidence`: `high` (the technique's own ATT&CK description names
28
+ this weakness class or a direct synonym), `medium` (a common,
29
+ well-established path but not a definitional match), or `low` (plausible
30
+ but more context-dependent).
31
+ - `rationale`: one sentence, so a reader can judge the mapping rather
32
+ than trust it.
33
+ 3. `icsprio.join` keeps only the highest-confidence mapping per technique
34
+ when multiple CWEs on the same CVE point to the same technique, and
35
+ drops any mapped technique that isn't in the currently-fetched ATT&CK
36
+ catalog (so a stale crosswalk entry can never claim a retired ID).
37
+
38
+ ## Coverage
39
+
40
+ The initial crosswalk (`icsprio/reference_data/cwe_to_attack_ics.csv`) covers
41
+ roughly the two dozen CWEs most frequently assigned in CISA ICS advisories
42
+ and Vulnrichment records (hard-coded/missing credentials, injection
43
+ families, memory-safety bugs, denial-of-service-prone weaknesses,
44
+ cryptographic/transport weaknesses, and access-control failures). A CWE not
45
+ in the table simply contributes no mapped technique — this is silent by
46
+ design (see NEXT_STEPS.md for surfacing "unmapped CWE" explicitly).
47
+
48
+ ## Extending it
49
+
50
+ Add a row to `icsprio/reference_data/cwe_to_attack_ics.csv` with the same five
51
+ columns (`cwe_id, cwe_name, attack_technique_id, attack_technique_name,
52
+ confidence, rationale`) and re-run `icsprio build` — no code change needed.
53
+ `tests/test_attack_mapping.py` confirms the loader and dedup logic handle
54
+ new rows correctly.
55
+
56
+ ## Terms of use
57
+
58
+ ATT&CK for ICS content (technique names, IDs, tactics) is used under the
59
+ [ATT&CK Terms of Use](https://attack.mitre.org/resources/terms-of-use/),
60
+ which permits use with attribution to MITRE ATT&CK.
@@ -0,0 +1,44 @@
1
+ # Codebook — `data/processed/icsprio_joined.csv`
2
+
3
+ One row per (advisory, CVE) pair. An advisory with no listed CVE keeps one
4
+ row with `cve` empty.
5
+
6
+ | Column | Type | Source | Definition |
7
+ |---|---|---|---|
8
+ | `advisory_id` | string | CISA CSAF | Advisory tracking ID, e.g. `ICSA-26-076-03`. |
9
+ | `advisory_type` | string | derived | `ICSA` (general ICS/OT) or `ICSMA` (medical device), from the ID prefix. |
10
+ | `title` | string | CISA CSAF | Advisory document title. |
11
+ | `published` | date | CISA CSAF | `document.tracking.initial_release_date`. |
12
+ | `updated` | date | CISA CSAF | `document.tracking.current_release_date`. |
13
+ | `vendors` | list[string] | CISA CSAF | Vendor name(s) from the advisory's product tree. |
14
+ | `products` | list[string] | CISA CSAF | Affected product name(s). |
15
+ | `advisory_url` | string | derived | Human-readable advisory page. |
16
+ | `cve` | string | CISA CSAF | The CVE ID this row is about. |
17
+ | `in_kev` | bool | CISA KEV | Whether `cve` appears in the KEV catalog at fetch time. |
18
+ | `kev_date_added` | date | CISA KEV | Date CISA added this CVE to KEV, if applicable. |
19
+ | `kev_due_date` | date | CISA KEV | Federal remediation due date (Binding Operational Directive scope), if applicable. |
20
+ | `kev_ransomware_use` | string | CISA KEV | `Known` or `Unknown` — known use in ransomware campaigns. |
21
+ | `epss_score` | float [0,1] | FIRST EPSS | Estimated 30-day exploitation probability, at fetch time. |
22
+ | `epss_percentile` | float [0,1] | FIRST EPSS | This CVE's percentile rank among all scored CVEs, at fetch time. |
23
+ | `epss_date` | date | FIRST EPSS | The date EPSS scored this CVE (EPSS is point-in-time — see LIMITATIONS). |
24
+ | `ssvc_exploitation` | categorical | Vulnrichment | CISA-ADP's SSVC Exploitation decision point: `none` / `poc` / `active`. |
25
+ | `ssvc_automatable` | categorical | Vulnrichment | SSVC Automatable decision point: `no` / `yes`. |
26
+ | `ssvc_technical_impact` | categorical | Vulnrichment | SSVC Technical Impact decision point: `partial` / `total`. |
27
+ | `cvss_base_score` | float [0,10] | Vulnrichment | CVSS base score from the CISA-ADP container, when present. |
28
+ | `cvss_vector` | string | Vulnrichment | CVSS vector string. |
29
+ | `cwes` | list[string] | Vulnrichment | CWE weakness ID(s) CISA-ADP assigned to this CVE. |
30
+ | `attack_technique_ids` | list[string] | derived | ATT&CK-for-ICS technique IDs mapped from `cwes` via the heuristic crosswalk — see ATTACK_MAPPING.md. **Not** a MITRE or CISA mapping. |
31
+ | `attack_technique_names` | list[string] | derived | Names matching `attack_technique_ids`. |
32
+ | `priority_score` | float [0,100] | derived | icsprio's own OT-relevant priority score — see SCORING.md. |
33
+ | `priority_band` | categorical | derived | `Critical` / `High` / `Medium` / `Low`, from `priority_score` thresholds in SCORING.md. |
34
+
35
+ ## Missing values
36
+
37
+ - `epss_score`/`epss_percentile`/`epss_date` are null when a CVE is too new
38
+ or otherwise unscored by EPSS at fetch time — not zero.
39
+ - `ssvc_*`, `cvss_*`, and `cwes` are null/empty when Vulnrichment has not
40
+ yet enriched that CVE — CISA-ADP enriches a subset of CVEs, prioritizing
41
+ recent and higher-signal ones, not every CVE ever published.
42
+ - `attack_technique_ids` is empty both when a CVE has no CWE assignment and
43
+ when its CWE(s) aren't yet in the crosswalk table — these two cases are
44
+ not currently distinguished in the output; see NEXT_STEPS.md.